Tender Notice Deadline Review Queue
Pricing
from $17.50 / 1,000 notice assessments
Tender Notice Deadline Review Queue
Review existing tender datasets for duplicate notices, field conflicts and missing deadlines. Compare valid dates against your chosen reference date and retain evidence per notice assessment.
Pricing
from $17.50 / 1,000 notice assessments
Rating
0.0
(0)
Developer
Tim Zinin
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
15 hours ago
Last modified
Categories
Share
Tender Notice Deadline Review Queue — reconcile duplicate TED notices from two feeds into one deadline-ordered review queue
Feed this Actor tender-notice rows from your own zinin/gov-tender-radar and zinin/tender-alert feeds (or any source using the same shape), and it returns one deduplicated notice assessment per literal publicationNumber, reconciled buyer/title/deadline/CPV fields with exact conflict evidence, and an honest deadline classification against your own comparison date — ordered soonest-deadline-first so your procurement team reviews the notices that actually need attention before anyone opens a spreadsheet.

What you get
You give this Actor a dataset (or an inline list) of tender-notice rows — the shape produced by zinin/gov-tender-radar (recordType: "tender_observation") and zinin/tender-alert (recordType: "new_tender"), or any other source that carries the same fields. It never scrapes TED itself, never calls a model, and never invents a value your rows did not supply — it reads exactly the rows you give it and reports, per literal publicationNumber, a notice assessment: a single reconciled view of that notice across every contributing row, with every figure traceable back to the specific source row that produced it.
Concretely, for each notice identity observed in your batch, you receive:
identityNamespace/publicationNumber—'TED'when a row carries the exact TED publication-number shape (\d{4,10}-\d{4}) and ahttps://ted.europa.eu/...URL the two known adapters actually emit;'unclassified'for any otherrecordType/publicationNumbercombination, keyed by its ownrecordTypeso an unrelated numbering scheme can never silently collide with a genuine TED number. No fuzzy title merge is ever performed — two rows reconcile together only when this exact identity string matches.title/buyer/buyerCountry/cpv/publicationDate/url— each reconciled independently across every accepted row sharing this identity, from whichever row has the freshest validcheckedAttimestamp. Ties (or rows with no timestamp) break on the full raw value content, never row order.deadline— the reconciled deadline exactly as published (YYYY-MM-DDwith an optional UTC offset, e.g.2026-10-21+02:00). The offset is display-only; comparisons againstasOfalways use the calendar date as published.deadlineStatus/daysUntilDeadline—deadline_unknownwhen no row supplied a parseable deadline,deadline_present_not_comparedwhen a deadline exists butasOfwas omitted, oroverdue/due_today/upcomingonce a strictYYYY-MM-DDasOfis supplied.daysUntilDeadlineis the exact day-count difference,nullunless both are present.deadlineInvalidObserved—truewhen a contributing row's deadline string failed strict calendar validation; the raw string is preserved inevidencebut never used as the reconciled deadline.cpvCodes/cpvCodesOmittedCount— the reconciled CPV string split into individual codes before bounding, capped to 20 for display with an honest omitted count. Classification codes only — never eligibility or budget signals.conflicts/conflictingFields— every field where contributing rows genuinely disagreed, up to five distinct values (each tagged to its source row index) with an exactomittedValueCount. Any nonzero omitted count setspartial.missingFields— field names where every contributing row left the field empty or absent.sourceRowCount/sourceRowIndexes/sourceRowIndexesOmittedCount/sourcesObserved— how many rows contributed (before the 50-row display cap), which original batch offsets, and the distinctrecordTypestrings observed.
What this Actor deliberately does not do, so you never mistake a duplicate-cleanup convenience for a procurement decision:
- It does not guarantee any deadline is still valid, still open for bidding, or that the notice remains eligible for a response — it reports exactly what your source rows said, classified against your own
asOfdate, nothing more. - It does not assess bid fitness, award probability, buyer creditworthiness, or legal compliance of any kind.
action/confidenceare review-routing hints, never procurement or legal advice. - It does not merge a TED-shaped row into the same identity as an unclassified row sharing the same digit string — the TED namespace is reserved strictly for the two known adapters' real URL/number shape.
- It does not call any network, AI model, or system clock from inside its transform step. Every field in the output traces back to a value on a row you supplied;
daysUntilDeadlineis pure date arithmetic against theasOfstring you gave it, never today's real calendar date. - It does not launch
zinin/gov-tender-radar,zinin/tender-alert, or any other upstream Actor on your behalf. It never starts new scraping work and never combines multiple dataset IDs automatically on a single run.

Who uses it
- Public-sector bid teams who already run
zinin/gov-tender-radarand/orzinin/tender-alerton the same CPV/keyword watch and need one deduplicated queue instead of two overlapping raw feeds — sorted so the notices closest to their deadline surface first, every morning, without a human cross-referencing two spreadsheets. - Procurement operations analysts at agencies or suppliers who need an honest conflict report before trusting a buyer name or deadline that two feeds disagree on —
conflictsnames the exact field and both disputed values, with the source row each came from, rather than silently picking one. - Business-development coordinators at IT/services vendors tracking software, consulting, or IT-services tenders (the prefilled sample's real notices span Germany, Lithuania, Norway, Sweden and Spain) who want a single ranked worklist their team can triage in minutes instead of re-reading raw TED extracts.
- Data engineers building an internal tender dashboard who need a dependable, pure post-processing step between two scrapers' raw output and a BI table — one that never silently drops a disagreement between feeds and always states exactly how many rows or conflict values were capped.
- Search-term note for procurement-monitoring teams: if your own vocabulary uses terms like "TED tender deduplication", "public procurement notice review queue", "tender deadline tracker", "CPV notice reconciliation", or "gov tender radar duplicate cleanup", this Actor's identity and reconciliation rules apply directly to that workflow without any extra configuration.
How to run
- Open the Apify Console and start a new run of Tender Notice Deadline Review Queue.
- Choose a source, in order of precedence (an explicit dataset always wins over a run reference, which always wins over the inline example — a dataset/run source never merges with inline rows):
- Source Dataset (
datasetId) — point at an existing Dataset from azinin/gov-tender-radarorzinin/tender-alertrun (or both, combined by you into one dataset before this run — this Actor does not auto-concatenate multiple dataset IDs). - Source run ID (
runId) — supply a run ID instead if you have one but not its Dataset ID directly; its default Dataset is resolved automatically, using your own run's granted permissions. - Integration payload (
payload.resource) — set automatically by Apify's Integrations "Actor/task run" template when you chain this Actor after an upstream scraper; carries{{resource.defaultDatasetId}}/{{resource.id}}/{{resource.actId}}so no manual mapping is needed. - Source records (inline) — paste or edit rows directly, or leave the prefilled real public sample in place for a first test run: six real
tender_observation/new_tenderrows drawn from actualzinin/gov-tender-radarandzinin/tender-alertruns.
- Source Dataset (
- Optionally set Comparison date (
asOf) to a strictYYYY-MM-DDcalendar date if you want deadlines classifiedoverdue/due_today/upcoming— leave it blank to receive every known deadline asdeadline_present_not_compared(still shown, never guessed at). - Optionally set Max source rows (
maxInputRows, default 100, up to 1000) and Max results (maxResults, default 2, up to 1000). The platform's own Max total charge USD run option is a separate, independent stop: whichever limit is hit first ends delivery. - Run it. Delivered rows land in the run's default Dataset, one per notice identity, soonest known deadline first, each carrying
sourceRowIndexesback into your original batch so you can trace any figure to its source rows. - Read
partial,conflicts, and the summary'sresultsCapped/sourcePartialflags before treating a run as a complete, conflict-free picture of your batch — see Evidence and boundaries for exactly when those flags are set and why.
For a repeatable pipeline (e.g. a daily tender-watch refresh), save this configuration as an Apify Task and chain it after your zinin/gov-tender-radar/zinin/tender-alert runs via Apify's Integrations, or trigger it from your own system via the API — see Integration recipes below for both paths with the exact chaining fields.
Pricing
This Actor bills on the Apify Pay-Per-Event model under a single primary event, result-found — described to buyers under the pricing noun "notice assessment." There is no start fee and no per-row charge: only a delivered, useful notice assessment is billed. Free diagnostics (invalid rows, missing identity, insufficient evidence, invalid asOf, source failures, budget stops, empty-batch outcomes) never request the paid event and are never charged.
| Tier | Price per notice assessment |
|---|---|
| Free | $0.0250 |
| Bronze | $0.0225 |
| Silver | $0.0200 |
| Gold | $0.0175 |
| Platinum | $0.0175 |
| Diamond | $0.0175 |
Bronze, Silver and Gold apply Apify's standard 10%/20%/30% volume discounts off the Free-tier price; Platinum and Diamond are held at the Gold rate. There is no owner-run revenue claim and no guarantee of any particular run volume in this pricing table — it states only the price actually configured on this Actor's result-found event, verified against the live Actor record at publication time.
A useful notice assessment is a real aggregate result — a reconciled, evidence-linked notice with at least one usable substantive field — never a raw source row passed through unprocessed. A partial: true notice assessment (for example, one delivered while maxInputRows cut off part of your batch, or one whose conflicts exceeded the display cap) is still a useful aggregate and is still billed; only genuinely empty, invalid, or source-failure outcomes are free. See Evidence and boundaries for the exact delivery-and-charging mechanics, including why a new run of this Actor against identical input can bill again.
Input contract
This Actor's input schema combines the shared source contract with one product-specific field, asOf.
{"items": [{"recordType": "tender_observation","schemaVersion": "1.0.0","found": true,"checkedAt": "2026-09-22T05:18:26.811Z","publicationNumber": "650560-2026","title": "Germany – Computer-related services – SOC","buyer": "GovTech Platforms GmbH","buyerCountry": "DEU","cpv": "72500000, 72591000, 72220000, 72267000","deadline": "2026-10-21+02:00","publicationDate": "2026-09-22+02:00","url": "https://ted.europa.eu/en/notice/-/detail/650560-2026"},{"recordType": "new_tender","schemaVersion": "1.0.0","found": true,"checkedAt": "2026-09-22T05:40:14.689Z","publicationNumber": "650529-2026","title": "Sweden – IT services: consulting, software development, Internet and support – Ramavtal för webbtjänster","buyer": "Myndigheten för delaktighet","buyerCountry": "SWE","cpv": "72000000, 72413000","deadline": null,"publicationDate": "2026-09-22+02:00","url": "https://ted.europa.eu/en/notice/-/detail/650529-2026"}],"maxInputRows": 100,"maxResults": 2,"asOf": null}
This example is a trimmed projection of the Actor's own real prefill (the full prefill carries six real rows — three zinin/gov-tender-radar tender_observation rows and three zinin/tender-alert new_tender rows, each with its own __source provenance block naming the exact upstream actor/run/dataset). Every publicationNumber, title, buyer, and deadline in the prefill is a real value observed from a real upstream run on 2026-09-22, not synthetic fixture data.
| Field | Type | Notes |
|---|---|---|
items | array, optional | Inline source rows. Ignored if datasetId or a resolvable runId/payload.resource is present. Bounded to 1000 rows / 4 MiB total / 64 KiB per row. |
datasetId | string, optional | Reads an existing Dataset with your run's own granted permissions (resourcePicker, READ). Overrides inline items. For Integrations, map {{resource.defaultDatasetId}} here. |
runId | string, optional | Reads a run's default Dataset when you have a run ID but not its Dataset ID directly. No elevated access — the run's own default Dataset is resolved and read exactly as datasetId would be. |
payload | object, optional | A supplied Integrations/webhook envelope with resource.id, resource.defaultDatasetId, and resource.actId. The default does not fetch a source or create an envelope. Private Dataset access requires the destination run’s explicit READ grant. |
maxInputRows | integer, default 100, max 1000 | Hard cap on source rows read for this run, independent of maxResults. |
maxResults | integer, default 2, max 1000 | Hard cap on paid notice assessments delivered this run. Free diagnostics are never counted against this cap. |
asOf | string, optional | Strict YYYY-MM-DD calendar date (no time, no offset). Absent means deadline comparison is not performed — every known deadline is reported deadline_present_not_compared, never guessed as overdue or upcoming. A malformed asOf fails the run with a free invalid_input diagnostic before any row is read. |
Recognized row shapes
| Field | Which source carries it | Role in the assessment |
|---|---|---|
publicationNumber | both | Identity — the exact string grouped on; TED namespace requires the real TED digit-hyphen-year shape plus URL |
recordType | both | Distinguishes tender_observation (zinin/gov-tender-radar) from new_tender (zinin/tender-alert); recorded in sourcesObserved; also the unclassified-namespace key |
title / buyer / buyerCountry / cpv / publicationDate / url | both | Reconciled fields — freshest valid checkedAt wins, full-value tie-break otherwise |
deadline | both | Deadline basis — strict YYYY-MM-DD[±HH:MM]; unparseable values never become the reconciled deadline but are preserved as evidence |
checkedAt | both | Source capture timestamp used only for the freshness tie-break |
found / error / failureType | both | A row reporting a source failure is excluded entirely and reported as a free source_row_failed diagnostic |
partial / sourceComplete / queriesFailed | either | When signaling incompleteness, sets this bucket's partial flag before evidence/conflict capping |
Any other field present on a row — sourceQuery, isNew, baselineCreated, sourceStatus, queriesRequested, queriesSucceeded, and similar — is read but never surfaced in the output.
Output examples
Every row and diagnostic below is copied verbatim from real private cloud canary runs of this Actor's value-add-candidate build, never hand-written. Field order and values match the real Dataset item exactly, one example per listed evidence scenario.
Happy path — the full six-row prefill, maxResults: 2 (default)
Real run against the complete real prefill with default maxResults: 2 and no asOf supplied — the run's summary reported "requested": 6, "usefulResults": 6, "delivered": 2, "paid": 2, "free": 1, "resultsCapped": true, "partialOverall": true", a real, honest example of maxResults capping a batch that had four more useful notices than the run was configured to pay for. The first delivered row (soonest-published, alphabetically first once sorted with the other row of the same deadline_present_not_compared status):
{"entityId": "TED:650560-2026","found": true,"status": "deadline_present_not_compared","action": "review_unscheduled","confidence": "high","sourceRowCount": 1,"sourceRowIndexesOmittedCount": 0,"identityNamespace": "TED","publicationNumber": "650560-2026","title": "Germany – Computer-related services – SOC","buyer": "GovTech Platforms GmbH","buyerCountry": "DEU","cpv": "72500000, 72591000, 72220000, 72267000","cpvCodes": ["72500000","72591000","72220000","72267000"],"cpvCodesOmittedCount": 0,"publicationDate": "2026-09-22+02:00","url": "https://ted.europa.eu/en/notice/-/detail/650560-2026","deadline": "2026-10-21+02:00","deadlineInvalidObserved": false,"deadlineStatus": "deadline_present_not_compared","daysUntilDeadline": null,"asOf": null,"conflicts": [],"conflictingFields": [],"missingFields": [],"sourcesObserved": ["tender_observation"],"evidence": [{"field": "title","value": "Germany – Computer-related services – SOC","rule": "freshest_observation","sourceRowIndex": 0},{"field": "buyer","value": "GovTech Platforms GmbH","rule": "freshest_observation","sourceRowIndex": 0},{"field": "deadline","value": "2026-10-21+02:00","rule": "freshest_observation","sourceRowIndex": 0}],"businessPartial": false,"sourceRowIndexes": [0],"partial": false,"sourceCoverage": {"complete": true,"reason": null,"droppedOversized": 0},"product": "tender-notice-deadline-review-queue","schemaVersion": "1.0","sourceDatasetId": null,"sourceRunId": null,"suppliedSourceRunId": null,"sourceRunIdVerified": null,"processedAt": "2026-09-23T08:04:07.963Z"}
This notice's single contributing row never had asOf compared against it, so deadlineStatus is honestly deadline_present_not_compared rather than guessed — the real deadline (2026-10-21+02:00) is still shown in full. confidence: "high" reflects a TED-namespaced identity with zero conflicts across its one contributing row; missingFields/conflicts are both empty because this single-row notice has no disagreement to report.
Partial path — a real maxInputRows: 1 cutoff, with a genuinely delivered result
Real run against the same six-row batch with maxInputRows: 1 (so only the batch's first row was actually read) and maxResults: 2. The run wrote a free partial_source diagnostic ("reason": "max_input_rows_reached") plus the one notice it could reconcile from the single row it read:
{"entityId": "TED:650560-2026","found": true,"status": "deadline_present_not_compared","action": "review_unscheduled","confidence": "high","sourceRowCount": 1,"sourceRowIndexesOmittedCount": 0,"identityNamespace": "TED","publicationNumber": "650560-2026","title": "Germany – Computer-related services – SOC","buyer": "GovTech Platforms GmbH","buyerCountry": "DEU","cpv": "72500000, 72591000, 72220000, 72267000","cpvCodes": ["72500000","72591000","72220000","72267000"],"cpvCodesOmittedCount": 0,"publicationDate": "2026-09-22+02:00","url": "https://ted.europa.eu/en/notice/-/detail/650560-2026","deadline": "2026-10-21+02:00","deadlineInvalidObserved": false,"deadlineStatus": "deadline_present_not_compared","daysUntilDeadline": null,"asOf": null,"conflicts": [],"conflictingFields": [],"missingFields": [],"sourcesObserved": ["tender_observation"],"evidence": [{"field": "title","value": "Germany – Computer-related services – SOC","rule": "freshest_observation","sourceRowIndex": 0},{"field": "buyer","value": "GovTech Platforms GmbH","rule": "freshest_observation","sourceRowIndex": 0},{"field": "deadline","value": "2026-10-21+02:00","rule": "freshest_observation","sourceRowIndex": 0}],"businessPartial": false,"sourceRowIndexes": [0],"partial": true,"sourceCoverage": {"complete": false,"reason": "max_input_rows_reached","droppedOversized": 0},"product": "tender-notice-deadline-review-queue","schemaVersion": "1.0","sourceDatasetId": null,"sourceRunId": null,"suppliedSourceRunId": null,"sourceRunIdVerified": null,"processedAt": "2026-09-23T08:05:27.912Z"}
This row's business content is identical to the happy-path row above — the one row this narrower run actually read was the same first row — but partial is now true and sourceCoverage names the exact reason (max_input_rows_reached). This states explicitly that the batch's remaining five real rows (two more tender_observation, three new_tender) were never read by this run — not dropped, corrupted, or degraded, simply outside the row window configured for this run. Note on the underlying evidence file name: this scenario's saved evidence is filenamed partial-columns-r2 for historical reasons, but the real cause is a row-count cutoff (maxInputRows: 1), not any column being deleted from the row that was read — every field this Actor recognizes on that one row is reconciled and shown in full above.
Failure path — the platform's own charge limit stops delivery before any row is read
Real run with the platform's Max total charge USD run option set to $0.005 — below the configured $0.025 Free-tier price of a single notice assessment:
{"product": "tender-notice-deadline-review-queue","schemaVersion": "1.0","status": "budget_stopped","reason": "Run charge limit cannot cover any result; no source was read.","found": false,"processedAt": "2026-09-23T08:05:01.770Z"}
This diagnostic is free — the real run's terminal record reported "paid": 0, "free": 1, "budgetStopped": true. Critically, the Actor checks whether the run's own charge limit can cover even a single result before reading any source rows, so a misconfigured (too-low) Max total charge USD fails safely and instantly rather than partially reading your batch and then stalling mid-run.
Empty-source path — a batch with nothing usable in it
Real run with items: [] (an explicitly empty inline batch):
{"product": "tender-notice-deadline-review-queue","schemaVersion": "1.0","status": "no_results","found": false,"reason": "no_usable_rows_in_batch","sourceCoverage": {"complete": true,"reason": null,"droppedOversized": 0},"processedAt": "2026-09-23T08:04:52.776Z"}
Also free. sourceCoverage.complete: true here means exactly what it says: the Actor read the entire (empty) batch you supplied — the emptiness is a fact about your input, not a read failure on the Actor's part.
Field dictionary
| Field | Meaning |
|---|---|
entityId | <namespace>:<publicationNumber> — stable identity string (e.g. TED:650560-2026). |
found | true for a useful result eligible for the result-found event; every free diagnostic row has found: false. |
status | Mirrors deadlineStatus on a delivered row; otherwise a diagnostic status listed under Evidence and boundaries. |
action | review_overdue, review_before_deadline, or review_unscheduled — a routing hint, never a bid or legal decision. |
confidence | high, medium, or low — see Decision routing. |
sourceRowCount | Total accepted rows contributing to this notice, before the sourceRowIndexes display cap. |
identityNamespace | TED for the known adapters' genuine shape; unclassified for anything else, keyed by its own recordType. |
publicationNumber | The exact identity string grouped on. No fuzzy title merge. |
title / buyer / buyerCountry / publicationDate / url | Reconciled from the freshest valid checkedAt observation; null when no row supplied a value. |
cpv | The reconciled raw CPV string, unchanged. |
cpvCodes / cpvCodesOmittedCount | cpv split into codes, capped to 20, honest omitted count computed before capping. |
deadline | The reconciled deadline exactly as published, offset preserved for display only. |
deadlineInvalidObserved | true when a contributing row's deadline string failed strict validation; never used as the reconciled value. |
deadlineStatus | deadline_unknown, deadline_present_not_compared, or overdue/due_today/upcoming when asOf is supplied. |
daysUntilDeadline | deadline minus asOf in whole calendar days; null unless both valid. Negative means overdue. |
asOf | Echoes the exact asOf string supplied, or null when omitted. |
conflicts | Array of { field, values: [{ value, sourceRowIndex }] (cap 5), omittedValueCount }, aggregated across the full batch before display capping. |
conflictingFields | Field names present in conflicts, for quick filtering. |
missingFields | Field names where every contributing row left the field empty or absent. |
sourceRowIndexes / sourceRowIndexesOmittedCount | First 50 original batch offsets contributing to this notice, plus exact count beyond that cap. |
sourcesObserved | Distinct recordType strings observed, sorted (e.g. ["new_tender", "tender_observation"]). |
evidence | Bounded list (cap 12) of { field, value, rule, sourceRowIndex } — reconciled title/buyer/deadline plus unparseable-deadline entries. |
businessPartial | Reserved product-level partial flag, distinct from row-level partial. |
partial | true when a contributing row signaled incompleteness, the row window wasn't fully read, an invalid deadline was observed, a conflict field had an omitted value, or CPV codes were capped. |
sourceCoverage | { complete, reason, droppedOversized } — whether the reader established the end of the supplied source, including a bounded probe at a row limit. |
product / schemaVersion | Fixed identifiers ("tender-notice-deadline-review-queue" / "1.0") for schema pinning. |
sourceDatasetId / sourceRunId / suppliedSourceRunId / sourceRunIdVerified | Provenance for a dataset/run-sourced batch — null on an inline-items run. |
processedAt | When this Actor processed the row — never a publication or deadline date. |
Diagnostics (never billed) share a smaller, honest shape: { product, schemaVersion, status, found: false, reason, sourceRowIndexes? }, with status one of invalid_row, source_row_failed, missing_identity, insufficient_evidence, invalid_input, no_results, budget_stopped, partial_source, result_dropped_oversized, or the run-level max_results_reached summary row.
Evidence and boundaries
What "found" actually means here. A delivered notice assessment requires found: true with a nonempty entityId/status/action, populated evidence, and valid sourceRowIndexes — never fabricated from a batch that contained nothing usable for a given identity.
Why a bare publicationNumber alone is never enough. Every accepted row must carry at least one of title, buyer, deadline, publicationDate, or cpv in addition to its identity — url is deliberately excluded, since it is a reference pointer never fetched, not a described notice attribute. A row with only a publicationNumber and a url is a free insufficient_evidence diagnostic, never a paid notice, and this applies to every namespace including a genuinely TED-shaped row.
Diagnostics carry no result event. The transform records rejections, but delivery is bounded to200 diagnostics and64KiB per row; the summary reports truncation/oversized omissions. After storage failures a summary itself may be unavailable. Every rejected row gets a specific status, never a silent drop: invalid_row (not an object), source_row_failed (source reported found: false, a nonempty failureType, or a truthy error), missing_identity (no usable publicationNumber, or one exceeding 100 characters / containing control characters), or insufficient_evidence (identity present, no substantive field). A malformed asOf produces a run-level invalid_input diagnostic before any row is read.
Why maxInputRows/maxResults caps and platform charge limits interact the way they do. This Actor reads at most maxInputRows source rows and delivers at most maxResults paid notice assessments, independent of each other and both independent of the platform's own Max total charge USD run option — whichever bound is hit first ends delivery, and the delivered row(s) or diagnostic tell you exactly which one it was (max_input_rows_reached, max_results_reached, or budget_stopped). Before reading any source row, the Actor verifies the run's charge limit can cover at least one notice assessment at the confirmed live price; if it cannot, the run stops immediately with the free budget_stopped diagnostic shown above rather than reading part of your batch and then stalling.
Delivery is a dataset write, then an explicit charge — never the reverse, and never guaranteed exactly-once. Every delivered notice assessment is written to your run's Dataset before the corresponding result-found charge event is requested, and the two steps are not transactional. On any ambiguity about whether a write or a charge actually completed, this Actor fails closed rather than guessing — it never automatically retries a possibly-already-charged write, because a retry could double it. A run that fails partway through may still contain already-delivered, already-charged rows; that is expected for a partial run, not a bug. A same-run resurrection (e.g. after a platform restart mid-run) is refused by the shared billing ledger — a within-run guard only, never cross-run deduplication. A summary/OUTPUT persistence attempt can itself fail; when it does, the run reports failed honestly, even though earlier deliveries genuinely happened and were counted.
Re-running is new processing, not deduplication. Running this Actor again against the exact same input performs genuinely new processing and can bill again for the same notices. If you need to avoid double-billing across scheduled refreshes, dedupe on your own side using publicationNumber + identityNamespace, or only re-run against genuinely new upstream data.
Source resolution and permissions. Dataset references from datasetId/payload.resource.defaultDatasetId take precedence over run-only references and inline items. Conflicting same-kind IDs are rejected. Accessible run metadata is checked: a proven run/Dataset mismatch is rejected, while inaccessible metadata with a readable explicit Dataset permits processing with unverified run provenance. The Dataset resourcePicker READ grant enables private access; an envelope alone grants none. Source reads use bounded GET requests to https://api.apify.com. SDK output storage and billing perform writes. Tokens belong in Authorization headers, never in URLs.
Bounded everything, always. 1000 source rows / 4 MiB total input / 64 KiB per source row, per run. evidence capped at 12 entries per notice, conflicts[].values at 5 per field, cpvCodes at 20, sourceRowIndexes at 50 — every cap ships with an exact omitted count and sets partial: true whenever anything was actually omitted. Distinctness/freshness tie-breaks are always computed on the full raw value across the entire contributing row set, never a display-bounded copy.
What this Actor cannot verify. It cannot confirm a notice remains open, that a deadline has not since been extended or cancelled at the source, or that a buyer/title string names a real, currently-operating entity. It reports only what your rows said, classified against your chosen asOf — never a live TED lookup or a legal/eligibility opinion.
Decision routing
confidence is set by one deterministic rule, applied per notice identity, after the full batch for that identity has been aggregated:
high—identityNamespaceisTEDandconflictsis empty. A genuine known-adapter notice with no field disagreement across its contributing rows.medium—identityNamespaceisTEDbut at least one field is inconflicts. Still a known-adapter notice, but your two feeds disagreed on something real.low—identityNamespaceisunclassified, regardless of conflict state. An unrecognized adapter shape is inherently less certain, since this Actor cannot confirm it follows the same identity discipline as the two known adapters.
action is driven purely by deadlineStatus, independent of confidence:
review_overdue—deadlineStatus: "overdue"(only possible whenasOfwas supplied and the deadline calendar date precedes it).review_before_deadline—deadlineStatusisdue_todayorupcoming.review_unscheduled—deadlineStatusisdeadline_unknownordeadline_present_not_compared(no deadline known, orasOfwas not supplied).
Route accordingly:
confidence: "medium"→ checkconflictsbefore quoting the buyer name or deadline externally; the freshest-timestamp winner is a reasonable default, not a verified fact.confidence: "low"→ treat identity itself as provisional; the row didn't confirm the known TED shape, so even a clean-looking notice deserves a second look.action: "review_overdue"→ verify at the source before assuming the opportunity is truly closed, since your batch may simply be stale relative to today.action: "review_unscheduled"→ no deadline was captured, orasOfwas omitted; re-run withasOfset to your actual review date for a real urgency classification.
partial: true is an orthogonal signal from both confidence and action — it tells you the displayed evidence/conflict lists were capped, or your configured row window wasn't fully read this run, not that the underlying assessment is wrong. A high-confidence partial row must retain its coverage warning; unread source rows may change the review conclusion.
Commercial playbooks
Daily tender-watch triage. Chain zinin/gov-tender-radar and zinin/tender-alert → this Actor, scheduled daily via an Apify Task with asOf set to the actual run date. Route review_overdue/review_before_deadline rows into your team's daily standup list; review_unscheduled rows go into a lower-priority backlog.
Cross-feed data-quality gate before a bid go/no-go meeting. Run the relevant batch through this Actor before your team commits to pursuing a notice. conflicts and missingFields surface exactly the discrepancies (a buyer name spelled two ways, a deadline that shifted) that would otherwise cost lead time to notice manually mid-meeting.
Weekly CPV-segment digest. Feed a CPV-filtered batch (e.g. all 72xxxxxx IT-services notices) through this Actor weekly. cpvCodes and businessSummary.deadlineStatusCounts give a quick shape of how many upcoming vs. unscheduled notices exist that week — one input into a broader pipeline-planning memo.
Internal procurement-ops validation step. Insert this Actor immediately after your scraper(s), before rows reach a shared tracker or CRM. A medium/low confidence flags that notice for a human check before it loads into a system other teams trust at face value.
Integration recipes
Chained via Apify Integrations (recommended for scheduled pipelines). In the Apify Console, open your zinin/gov-tender-radar or zinin/tender-alert Task, add an "Actor/task run" integration pointed at this Actor, and map the upstream run's {{resource.defaultDatasetId}} into this Actor's Source Dataset field and supply any webhook envelope explicitly. The supplied references are reconciled. Accessible run metadata is checked against the Dataset; a proven mismatch is rejected. If run metadata is inaccessible but the explicit Dataset is readable, processing may continue with unverified run provenance (sourceRunIdVerified: false).
Direct API call. Start a run via POST https://api.apify.com/v2/acts/zinin~tender-notice-deadline-review-queue/runs?maxTotalChargeUsd=0.50 with your Authorization header, passing a JSON body such as:
{"datasetId": "<upstream dataset id>","maxInputRows": 200,"maxResults": 10,"asOf": "2026-10-01"}
Set maxTotalChargeUsd on the run options to a positive, bounded value covering at least one notice assessment at your account's tier. Poll GET /v2/actor-runs/<runId> for status: "SUCCEEDED", then read GET /v2/datasets/<defaultDatasetId>/items for the delivered rows.
Webhook-notified pipeline (template, not a live registration). Attach a webhook for ACTOR.RUN.SUCCEEDED, pointed at your own HTTPS endpoint, with doNotRetry: true and Authorization as a header your endpoint checks — never a credential embedded in the URL:
{"eventTypes": ["ACTOR.RUN.SUCCEEDED"],"requestUrl": "https://your-service.example.com/webhooks/tender-notice-review","headersTemplate": "{\"Authorization\": \"Bearer <YOUR_ENDPOINT_SECRET>\"}","doNotRetry": true}
The webhook payload itself never grants elevated access to the run's Dataset — treat it as a "go read the Dataset now" signal, and fetch rows via the Dataset API using your own stored token and the datasetId in the run's own metadata.
MCP / agent tool use. Pass the same datasetId/runId/items + maxInputRows/maxResults/asOf shape as any other caller. Surface confidence, action, and conflicts directly to the end user rather than only the deadline date — those fields distinguish "safe to route automatically" from "needs a human to check the source rows first."
Operating guide
- Start with the real prefill. Run it once against the prefilled six-row sample to confirm the delivered shape and routing rules match your expectations — it costs the price of two notice assessments (or less, at your account's tier) and uses genuinely real, previously-observed public data.
- Set
asOfto your actual review date, not a fixed value. An outdatedasOfon a scheduled Task silently misclassifies deadlines relative to the wrong reference point — update it on your own schedule cadence, or drive it from your own system via the API on each trigger. - Set
maxInputRowsto your batch's real combined size. The default of 100 exists for a safe first test; under-setting it silently caps coverage (sourceCoverage.reason: "max_input_rows_reached",partial: true) without failing the run. - Set
maxResultsto your real number of distinct notices. A real daily watch batch with dozens of distinct publication numbers needsmaxResultsraised, or you will seemax_results_reachedand only get a partial worklist back. - Always set the platform's own Max total charge USD for any run touching a batch you have not sized before — a cap below one result stops before source reads; a cap that covers only part of the batch can deliver that part and then stop. Inspect the budget and delivery counters.
- Treat
conflictsas a to-do list. Every conflict is a real disagreement your own upstream sources produced — most commonly the same notice scraped by both feeds with a slightly different title or buyer string. - Do not rely on a single run for a live tender pipeline. Deadlines reported
upcomingyesterday may reportoverduetoday purely because yourasOfmoved forward, not because the notice changed. - Combine both feeds before this run, not after. Cross-feed reconciliation only sees rows supplied in one run's batch — merge the
zinin/gov-tender-radarandzinin/tender-alertdatasets (or list both inline) before submitting; two separate runs defeat the duplicate-detection purpose.
FAQ
Does this Actor scrape TED or any tender source itself?
No. It is a pure post-processing reconciliation step over rows you already have — from zinin/gov-tender-radar, zinin/tender-alert, or a hand-built list following the recognized row shapes. It never launches an upstream Actor and never fetches anything from ted.europa.eu directly.
Why did two rows for what I know is the same real-world notice show up as two separate identities?
Either their publicationNumber strings genuinely differ, or one row is TED-shaped and the other is not (missing the real TED URL or digit-hyphen-year format), routing it into unclassified under its own recordType. Identity is deliberately literal and namespace-strict — no fuzzy title/buyer matching, because near-matches risk silently merging two genuinely different notices.
My asOf value was rejected with invalid_input. Why?
asOf must be a strict YYYY-MM-DD calendar date, no time, no offset — the same grammar deadline's date portion uses, minus the optional offset suffix deadline allows. A malformed value fails the entire run before any source row is read.
Why is daysUntilDeadline null even though the notice clearly has a deadline?
Because asOf was not supplied this run. daysUntilDeadline needs both a valid reconciled deadline and a valid asOf; with only one present the Actor reports deadline_present_not_compared honestly rather than assuming today's real calendar date.
What happens if I supply a datasetId and a runId that don't correspond to each other?
The run fails immediately with INVALID_INPUT / run_dataset_mismatch before any row is read. This Actor never guesses which one you "really meant."
Can I get more than 5 conflicting values or 50 source row indexes per notice?
Not in a single delivered row. conflicts[].omittedValueCount and sourceRowIndexesOmittedCount always tell you the exact count beyond the cap; partition that notice’s source rows into smaller batches and check omitted counts on each. A one-notice filter alone does not remove per-notice caps; deduplicate overlapping partitions before combining counts.
Will running this Actor again on the same dataset cost me twice?
Yes, if the same notices still qualify as useful assessments. There is no cross-run deduplication, only a same-run resurrection guard. Build your own dedupe key (publicationNumber + identityNamespace) for a recurring schedule against overlapping data.
Is a high confidence a guarantee the deadline is still accurate today?
No. confidence describes identity certainty and cross-feed agreement within your batch only — never a claim the source notice remains open or still accepting bids. Always verify a genuinely urgent deadline at the source.
Sources and rights
This Actor processes only tender-notice rows you supply — your own zinin/gov-tender-radar runs, zinin/tender-alert runs, or any hand-built inline list following the recognized row shapes above. It holds no scraping rights or licensing relationship of its own with TED or any national procurement portal; you remain responsible for the terms under which your source data was originally obtained.
Related public Store Actors referenced or usable alongside this one, verified against this project's own live upstream inventory at the time this page was written:
zinin/gov-tender-radar— the primary upstream source for this Actor'stender_observationrows.zinin/tender-alert— the primary upstream source for this Actor'snew_tenderrows.zinin/company-registry-enricher— a related public tool for verifying abuyername against real corporate-registry facts before treating a reconciled buyer identity as confirmed; not itself compatible source input for this Actor.zinin/sanctions-screening— a related public tool for screening a notice'sbuyeror a prospective bidder against public sanctions-adjacent sources as a separate due-diligence step; not itself compatible source input for this Actor.zinin/sec-edgar-watcher— a related public tool useful when a notice's buyer or an interested bidder is a US public company and you want SEC-filing context alongside the procurement review; not itself compatible source input for this Actor.
Public examples in this Actor's prefill and in this README are curated, provenance-tagged projections of actual public source runs captured on 2026-09-22, never synthetic fixtures — every value traces back to a real upstream Actor, run, and Dataset recorded in the row's own __source block, not a fresh scrape performed on every run of this Actor. The saved public projections exclude dates of birth, home addresses, nationality, and personal contact details. User-supplied records remain the caller’s responsibility.