EU Maritime & Port Opportunity Monitor avatar

EU Maritime & Port Opportunity Monitor

Pricing

from $100.00 / 1,000 qualified opportunities

Go to Apify Store
EU Maritime & Port Opportunity Monitor

EU Maritime & Port Opportunity Monitor

Monitor EU and Spanish port and maritime tenders: dredging, quays, breakwaters, shore power and coastal works. Qualified new tenders, deadline changes, awards and funding calls from official PLACSP, TED and EU Funding & Tenders data, via API or scheduled runs.

Pricing

from $100.00 / 1,000 qualified opportunities

Rating

0.0

(0)

Developer

Gabriel Spoleto Couto

Gabriel Spoleto Couto

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Monitor EU maritime tenders, port procurement, public contracts, and funding signals through one API-first Apify Actor. It finds technically relevant opportunities in official Spanish and European Union data, applies a deterministic maritime taxonomy, and emits traceable lifecycle changes for CRM, bid/no-bid, market-intelligence, and business-development workflows.

This is procurement intelligence for companies bidding for public contracts. It is not a jobs, vacancies, candidates or employment scraper.

The maritime procurement monitor is designed for marine contractors, engineering consultancies, dredging and coastal works specialists, port-equipment suppliers, shore-power providers, terminal operators, and business-development teams. It returns qualified lifecycle events instead of an unfiltered dump of public notices.

Version 0.2 scans each source incrementally, defers events beyond the run limits instead of dropping them, and qualifies opportunities with a stricter maritime classifier. Validate representative countries, sources, filters, and budgets before scheduling a production dependency.

Start small: run once without monitorId to get a snapshot of every qualified opportunity in the lookback window. For a recurring monitor, set a stable monitorId: the first successful observation can emit NEW events; later runs with the same monitorId emit only newly qualified or materially changed opportunities.

Maritime tender monitoring use cases

  • Feed new port tenders and deadline changes into a CRM or bid pipeline.
  • Track dredging, breakwater, quay, berth, terminal, VTS, and port-safety contracts.
  • Find shore-power, alternative-fuel, and port-decarbonisation opportunities.
  • Monitor pre-tender consultations before formal procurement opens.
  • Follow awards and cancellations for competitor and market intelligence.
  • Combine Spanish PLACSP notices, EU TED procurement, and Funding & Tenders signals in one dataset.
  • Schedule country, region, CPV, budget, and keyword-specific monitors for business-development teams.

What port and maritime tenders it monitors

  • Port, harbour, quay, berth, jetty, breakwater and terminal works.
  • Dredging, reclamation, coastal protection and marine construction.
  • Mooring, fendering, navigation, VTS/VTMS and port-safety systems.
  • Shore-side electricity, alternative fuels and port decarbonisation.
  • Maritime engineering, geotechnical and environmental services.

The built-in taxonomy combines maritime CPV roots, deterministic Spanish and English terminology, buyer/authority signals and user-supplied filters. Every result includes the matched rules; the Actor does not use an LLM or an opaque win-probability score.

Official tender sources: PLACSP, TED and EU Funding & Tenders

SourceSignalsInterface
PLACSPSpanish preliminary consultations, tenders, awards and cancellationsOfficial Atom/XML open data
TEDEU procurement notices and awardsOfficial Search API v3
EU Funding & TendersCalls and funded-project signals that may precede procurementOfficial public Search API

Source documentation: PLACSP open data, TED Search API, and Funding & Tenders APIs.

Input

{
"monitorId": "iberian-port-works",
"sources": ["placsp", "ted", "funding_tenders"],
"countries": ["ES", "PT"],
"regions": [],
"cpvCodes": ["45241000", "45252124"],
"keywords": ["dragado", "shore power"],
"minBudget": 100000,
"publishedAfter": "2026-07-01",
"signalTypes": ["PRE_TENDER", "OPEN", "AWARDED", "FUNDED_PROJECT"],
"maxResults": 100,
"maxScannedRecords": 500,
"minRelevanceScore": 0.35
}

Input fields

FieldTypeRequiredDefaultLimits and behavior
monitorIdstringNo—Stable state key, 3–80 characters: letters, numbers, _, or -. Omit it for a one-off snapshot.
sourcesstring[]Noall threeOne or more of placsp, ted, funding_tenders; maximum 3.
countriesstring[]No["ES"]ISO alpha-2 codes; 1–30 unique values. EL and UK are accepted as GR and GB.
regionsstring[]No[]Maximum 50. NUTS codes such as ES51 also match their subregions; region names match exactly.
cpvCodesstring[]No[]Maximum 100. A code matches its whole CPV branch: 4524 or 45240000 also match 45241000.
keywordsstring[]No[]Extra Spanish or English terms; maximum 100. Any term may match (OR).
minBudgetnumberNo—Minimum published budget. Records without a budget are excluded when set.
publishedAfterstringNoprevious 30 daysISO date in YYYY-MM-DD format for the first scan; later runs continue from the source cursors.
signalTypesstring[]No[] (all)Any of PRE_TENDER, OPEN, AWARDED, CANCELLED, FUNDED_PROJECT, UNKNOWN.
maxResultsintegerNo1001–1,000 events emitted per run, most relevant first; the rest are deferred to the next runs.
maxScannedRecordsintegerNo50050–5,000 upstream records scanned per source and run, independent of maxResults.
minRelevanceScorenumberNo0.35Deterministic classifier threshold from 0 to 1.
requestTimeoutSecondsintegerNo305–120 seconds for each official-source request.
maxConcurrencyintegerNo31–3 official sources queried concurrently.

Apify Console prefills maxResults with 20 and maxScannedRecords with 100 so that a first run finishes in about two minutes; API calls without these fields use the defaults above.

monitorId is stable state, not a display label. Reusing it compares the new scan with the last successful observation. Use a different ID for a different commercial profile. Without monitorId each run is an independent snapshot: it reports every qualified opportunity in the lookback window and remembers nothing for later runs.

Every source applies the same filter semantics. A filter excludes a record only when the record publishes that field and none of its values match: a notice without a CPV code is kept by a CPV filter, and the classifier then decides whether it is relevant. Keywords match the start of a word in the title or description, ignoring case and accents. NUTS codes are sent to TED; region names are applied locally.

The first run of a monitor scans the publishedAfter window (30 days by default). Each later run continues from where PLACSP and TED stopped, so it reads only new or changed notices; Funding & Tenders publishes no modification date and is rescanned within the window. Changing any relevance filter (countries, regions, CPV codes, keywords, budget, publishedAfter or the relevance score) restarts the lookback window. PLACSP publishes a newest-first feed and the Actor reads up to about three days of it per run, so schedule monitors at least daily. When a source reaches maxScannedRecords or its page limit, it reports PARTIAL in RUN_SUMMARY. Inputs are bounded to protect upstream services, run costs and storage.

Output

Each default Dataset item is a traceable change event. The following item is the exact serialized output produced by the v0.2 transition and event-building runtime for a deterministic TED fixture. It documents the response contract, not a current live notice or a performance claim; source data and matched rules can change between runs.

{
"recordType": "OPPORTUNITY_EVENT",
"schemaVersion": "2.0",
"eventType": "NEW",
"opportunityId": "ted:123456-2026",
"projectKey": "sha256:1c26485c91b370eb0d15814768250911fbfc04a65b1c41a980e39de4a25a2fec",
"source": "ted",
"sourceRecordId": "123456-2026",
"title": "Shore-side electricity installation at the port",
"description": null,
"phase": "OPEN",
"buyer": "Port Authority",
"supplier": null,
"countries": ["ES"],
"regions": [],
"cpvCodes": ["45241600"],
"budgetValue": 3200000.0,
"budgetCurrency": "EUR",
"publishedAt": "2026-08-09T10:00:00+00:00",
"deadlineAt": "2026-10-15T12:00:00+00:00",
"relevanceScore": 1.0,
"matchedRules": [
"category:port_infrastructure",
"category:marine_energy",
"keyword:port",
"keyword:shore-side electricity",
"cpv:45241600",
"score:CPV_MATCH",
"score:TITLE_TERM",
"score:BUYER_TERM",
"score:MULTI_SIGNAL"
],
"changedFields": [],
"sourceUrl": "https://api.ted.europa.eu/v3/notices/123456-2026",
"noticeUrl": "https://ted.europa.eu/en/notice/-/detail/123456-2026",
"sourceEvidence": [
{
"source": "ted",
"sourceRecordId": "123456-2026",
"sourceUrl": "https://api.ted.europa.eu/v3/notices/123456-2026",
"noticeUrl": "https://ted.europa.eu/en/notice/-/detail/123456-2026"
}
],
"firstSeenAt": "2026-08-09T10:00:00+00:00",
"lastSeenAt": "2026-08-09T10:00:00+00:00"
}

Supported change events are:

  • NEW: first qualified observation for this monitor.
  • UPDATED: one or more material fields changed.
  • DEADLINE_CHANGED: the submission deadline changed.
  • AWARDED: the source now reports an award.
  • CANCELLED: the source explicitly reports cancellation.

Absence from a source response is never interpreted as cancellation. Source health and counts are written to the default Key-Value Store under RUN_SUMMARY; historical state, source cursors and billing markers live in the named store derived from monitorId, or in the run's default store for a snapshot run. Besides health, errors and warnings, RUN_SUMMARY reports qualifiedCount (qualified event groups found), emittedCount, deferredCount (groups left for the next run by maxResults or the charge limit), chargeLimitReached, duplicateCount and sourceCursors.

Output fields

Field groupWhat it contains
IdentityopportunityId, cross-source projectKey, source, and sourceRecordId.
ChangeeventType, phase, changedFields, firstSeenAt, and lastSeenAt.
Opportunitytitle, optional description, buyer, supplier, countries, regions, CPV codes, budget, publication date, and deadline.
QualificationDeterministic relevanceScore and the exact matchedRules; this is not a probability of winning.
ProvenancesourceUrl, optional noticeUrl, and sourceEvidence[] for verification against official data.

A repeat scan can legitimately return an empty Dataset when no new or materially changed qualified opportunity is observed. Inspect RUN_SUMMARY to distinguish “no changes” from partial or failed source coverage.

Pricing

The Actor uses two Pay-Per-Event charges:

  • monitor-scan: USD 0.50 after at least one official source completes successfully.
  • qualified-opportunity: USD 0.10 for each persisted NEW or changed qualified opportunity.

Platform usage is included in these prices. A run's maximum cost must be at least USD 0.50 so that the scan itself can be charged.

Duplicates, irrelevant records and failed sources are not charged as qualified opportunities. A snapshot run without monitorId charges every qualified opportunity it reports, because it has no history to compare with. The Actor respects the run spending limit and uses idempotency keys so a restart cannot bill the same event twice. When the limit is reached, the remaining events are deferred to the next run instead of being dropped. Review the current Pricing tab and run a representative sample before scheduling a production monitor because pricing can change.

API integrations

The synchronous REST endpoint returns the default Dataset as a JSON array. These examples call the Actor and process lifecycle events rather than only printing the raw response. Set APIFY_TOKEN in your environment and keep monitorId stable across scheduled runs of the same commercial profile.

cURL

set -o pipefail
curl --fail-with-body -X POST \
"https://api.apify.com/v2/actors/gabichuuuuu~eu-maritime-port-opportunity-monitor/run-sync-get-dataset-items?clean=true" \
-H "Authorization: Bearer ${APIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"monitorId": "iberian-port-works",
"countries": ["ES", "PT"],
"keywords": ["dragado", "shore power"],
"signalTypes": ["PRE_TENDER", "OPEN", "AWARDED"],
"maxResults": 100
}' |
jq '[.[] | {
eventType,
title,
buyer,
phase,
budgetValue,
deadlineAt,
relevanceScore,
noticeUrl
}]'

Python

Install the official client with pip install apify-client:

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("gabichuuuuu/eu-maritime-port-opportunity-monitor").call(
run_input={
"monitorId": "iberian-port-works",
"countries": ["ES", "PT"],
"keywords": ["dragado", "shore power"],
"signalTypes": ["PRE_TENDER", "OPEN", "AWARDED"],
"maxResults": 100,
}
)
if run is None or run.get("status") != "SUCCEEDED":
status = None if run is None else run.get("status")
raise RuntimeError(f"The Actor run did not succeed: {status}")
events = list(client.dataset(run["defaultDatasetId"]).iterate_items())
for event in events:
crm_record = {
"external_id": event["opportunityId"],
"change": event["eventType"],
"title": event["title"],
"buyer": event.get("buyer"),
"deadline": event.get("deadlineAt"),
"source_url": event.get("noticeUrl") or event["sourceUrl"],
}
print(crm_record)
if not events:
print("No new or materially changed qualified opportunities")

Node.js

Node.js 18 or newer includes fetch. Save this ESM example as monitor.mjs and run it with node monitor.mjs:

const endpoint = new URL(
"https://api.apify.com/v2/actors/gabichuuuuu~eu-maritime-port-opportunity-monitor/run-sync-get-dataset-items",
);
endpoint.searchParams.set("clean", "true");
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
monitorId: "iberian-port-works",
countries: ["ES", "PT"],
keywords: ["dragado", "shore power"],
signalTypes: ["PRE_TENDER", "OPEN", "AWARDED"],
maxResults: 100,
}),
signal: AbortSignal.timeout(310_000),
});
if (!response.ok) {
throw new Error(`Apify ${response.status}: ${await response.text()}`);
}
const events = await response.json();
const crmRecords = events.map((event) => ({
externalId: event.opportunityId,
change: event.eventType,
title: event.title,
buyer: event.buyer,
deadline: event.deadlineAt,
sourceUrl: event.noticeUrl ?? event.sourceUrl,
}));
console.log(crmRecords);

Scheduling and no-change runs

Save the input as an Apify Task and schedule it at least daily. The default Dataset can be consumed through the Apify API, Make, Zapier or a run webhook. Webhook delivery is an Apify platform feature; this Actor does not send data to arbitrary user-provided URLs.

FAQ

How do I monitor port and maritime tenders in Spain and the EU?

Run the Actor once without monitorId to get a snapshot of every qualified opportunity in the lookback window. Then save the input as an Apify Task with a stable monitorId and schedule it at least daily: each scheduled run emits only new or materially changed opportunities.

Which public procurement sources does it cover?

Spanish public procurement from PLACSP (Plataforma de Contratación del Sector Público), EU procurement notices and awards from TED (Tenders Electronic Daily), and calls from the EU Funding & Tenders portal. All three are official, public sources; no login, proxy or paid data is used.

Can I get alerts for new tenders, deadline changes, awards and cancellations?

Yes. Every result carries a lifecycle event (NEW, UPDATED, DEADLINE_CHANGED, AWARDED or CANCELLED) that you can route to a CRM or bid pipeline through the Apify API, Make, Zapier or a run webhook.

Can I filter by CPV code, country, region, budget or keyword?

Yes. Combine countries, regions, cpvCodes, keywords, minBudget, publishedAfter, signalTypes and minRelevanceScore. A change of filters restarts the lookback window for that monitor.

Can I export the tenders to Excel, CSV or JSON?

Yes. Results are stored in the run's Apify Dataset, which you can download as JSON, CSV, Excel or XML from the Console or read through the Apify API.

Does it read tender documents or recommend whether to bid?

No. It qualifies the official notice data and links to the source; it does not interpret PDF tender documents, give legal advice or estimate a probability of winning.

Deliberate limits

  • No PDF or tender-document interpretation.
  • No private, authenticated or paid sources.
  • No browser automation or residential proxy.
  • No bid recommendation, legal advice or probability of winning.
  • No assumption that a missing record was cancelled.
  • Published values remain attributable to the official source and should be verified before bidding.

Development

Runtime: Python 3.13 on apify/actor-python:3.13. The code also supports Python 3.12 for local development.

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
.\.venv\Scripts\python.exe -m ruff check src tests scripts
.\.venv\Scripts\python.exe -m mypy src
.\.venv\Scripts\python.exe -m pytest

Run the real-source smoke script before a release. Fixtures test parser behavior; the smoke run tests the assumptions made about the live official services.

$env:PYTHONUTF8 = "1"
.\.venv\Scripts\python.exe scripts\smoke_sources.py

Architecture

The code separates source adapters from domain qualification and historical state:

official sources -> normalized source records -> maritime classifier
-> cross-source deduplication -> historical diff
-> Dataset / RUN_SUMMARY / PPE

This separation keeps one upstream schema change from changing the public output contract.