Mendoza Tenders Scraper & Monitor avatar

Mendoza Tenders Scraper & Monitor

Pricing

Pay per event

Go to Apify Store
Mendoza Tenders Scraper & Monitor

Mendoza Tenders Scraper & Monitor

Extracts public procurement processes (licitaciones, contrataciones directas) from the Province of Mendoza, Argentina COMPR.AR-based portal, with type, status, organism and budget per process.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Stefano Seggio

Stefano Seggio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 hours ago

Last modified

Share

Mendoza Tenders Scraper & Monitor — Apify Store Overview

Store URL: https://apify.com/stefano_seggio/mendoza-compras-monitor Actor ID: bb4cRgt1i27hvr9Ug Version: 2.0


Executive Summary & Business Use Case

Mendoza Tenders Scraper & Monitor extracts public procurement processes — licitaciones (public tenders) and contrataciones directas (direct-award contracts) — from the Province of Mendoza, Argentina's official COMPR.AR-based procurement portal, comprar.mendoza.gov.ar. For every process it walks, the actor returns the process number, name, type, status, executing organism, administrative/financial service, and budget amount, pulled from a real backlog of 25,000+ processes on the portal's results grid. Since the 2.0 release, it also runs a delta engine: on repeat runs it can report not just processes it has never seen before, but ones whose status changed (e.g. "Pendiente Análisis" → "Adjudicado") or whose content was amended (budget, opening date, executing unit), turning a one-off scrape into an ongoing monitor.

The most direct use case is supplier and bidder monitoring: companies that sell to Mendoza's provincial organisms (construction, health, IT, general services firms) can track specific processes and get notified the moment a tracked tender's estado moves to "Adjudicado" or its monto changes, instead of manually re-checking the portal. A second use case is bid consultants and gestores who manage tender pipelines for several clients simultaneously — a daily delta run surfaces exactly which of their clients' tracked processes changed status or were amended since yesterday, with a direct link to hand the client. A third is transparency and spending-pattern analysis: journalists, researchers, and public-accountability groups can use tipoProceso, unidadEjecutora, and monto across a large maxItems pull to see which organisms rely most heavily on direct-award contracting (which typically has less competitive oversight than open tenders) versus formal public tenders. A fourth, adjacent use is regional tender-data resellers and LATAM procurement platforms who would rather consume a structured, change-aware feed of a real ASP.NET postback-driven session flow than build and maintain that scraping logic themselves.

All of these use cases are grounded directly in what the source portal actually exposes: process type, status, organism, and a raw budget figure. The actor deliberately does not claim capabilities the source doesn't support — for example, it does not report a CLOSED event (a process disappearing from the register), because with 25,784+ total processes only a full sequential census could trustworthily prove a process is gone, and that census would take hours, not seconds — so that event was considered and explicitly rejected rather than faked.


Technical Features & V2 Architecture Highlights

Cross-run delta persistence. The actor opens a named Apify key-value store (not the run's own default, isolated-per-run store) to persist state between scheduled runs. As of v2.0, that state shape is entries: Record<numeroProceso, {estado, hash}> — for every process it has ever walked, it remembers the process's last-known estado and a content fingerprint. This is a breaking change from the v1 shape (a bare list of seen ids): a v1-shaped state is treated as absent rather than migrated, so an existing scheduled task's first v2 run re-baselines from scratch.

Real event types — three, not the generic four-event set. When onlyNew is on, every delivered record carries one of exactly three event types, read directly from event_type in the dataset schema:

  • NEW_LISTING — the process has never been seen by this actor before.
  • STATUS_CHANGE — the process was seen before and its estado changed since then (e.g. "Pendiente Análisis" → "Adjudicado"). This is detected for free: estado is already present in every grid row the actor walks, so no extra request is needed.
  • UPDATED — the process was seen before, estado is unchanged, but some other field changed (monto, fecha, unidad ejecutora, etc.), detected via a sha1 content fingerprint (contentHash) computed over the row's mutable fields.

A fourth value, UNCHANGED, appears only when onlyNew is off (full-mode runs) — it is not a delta-mode delivery option. Note what this actor's event set does not include: there is no CLOSED event, unlike some sibling actors in this actor family that operate on much smaller procurement registers. That omission is a deliberate design decision (documented in this actor's own CHANGELOG and AGENTS notes), not an oversight — proving a process has closed would require a complete sequential census of the ~25,784-process backlog, which is infeasible within a normal run's time budget.

onlyNew — a safe post-filter, not an early-stop optimization. Per this actor's own input schema, onlyNew persists which numeroProceso ids have already been returned — along with their last-known estado and content fingerprint — in the named key-value store, and keeps only records that are new, status-changed, or amended. Critically, and unlike a typical "delta mode," this does not make the actor stop paginating early. The portal's listing is sorted by numeroProceso ascending (each organism's own sequential counter, cycling through years), verified live to mix processes from 2019 through 2026 on page one of a blank search — not newest-first. So onlyNew fetches up to maxItems raw processes exactly as a normal run would, and only after each row is walked does it check the seen-set to decide whether to keep it. A brand-new process is not guaranteed to appear within a small maxItems window; raising maxItems is the way to cover more of the backlog on a delta run.

One genuinely expensive step, made optional. Unlike sources with a plain per-row link, Mendoza's results grid links are session-bound javascript:__doPostBack(...) calls with no plain href. Getting a real, stable, cookie-independent permalink (source_url) for a process means replaying that row's own postback — a real, measured cost of roughly 1.5 seconds per row. The resolveSourceUrl input (default true) makes this optional: disabling it skips the extra request per record, producing a much faster run when a process-specific link isn't needed, at the cost of source_url falling back to the generic search page. Every record's sourceUrlResolved field records whether the permalink was genuinely resolved this run or is the fallback.

Field count. The dataset schema defines 15 fields per record: 7 standardized B2B integration-envelope fields (record_id, event_type, scraped_at, is_new, contentHash, sourceUrlResolved, source_url) plus 8 domain-specific fields (numeroProceso, nombreProceso, tipoProceso, fechaApertura, estado, unidadEjecutora, servicioAdministrativoFinanciero, monto).


Input Schema & JSON Configuration Example

FieldTypeDefaultDescription
maxItemsinteger100Hard cap on the number of RAW processes walked this run (10 per page, 25,000+ total at audit time). This is the same cap as before - onlyNew/dateRange are applied on top of this raw walk as post-filters, so the number of records actually returned can be lower than maxItems when either is active. This actor does not yet expose the advanced-search form's own filters (date range, organism, process type) as actor input; start small, raise gradually.
onlyNewbooleanfalsePersists which numeroProceso ids this actor has already returned - and their last-known estado + content fingerprint - in its own private key-value store that survives between scheduled runs, and keeps only the ones that are new, status-changed or amended. IMPORTANT for this source: comprar.mendoza.gov.ar's process listing is sorted by numero de proceso ascending (each organism's own sequential counter, cycling through years) - NOT by date and NOT newest-first. Verified live: a blank-form search's first page mixes processes from 2019 through 2026 rather than showing only the most recent ones. Because of this, onlyNew is a safe POST-FILTER (fetch up to maxItems raw processes exactly as a normal run does, then drop the ones already seen and unchanged) rather than an early-stop optimization - it does NOT make pagination stop sooner, and it does NOT guarantee a newly created process will appear within a small maxItems window, since its position in the list depends on its own organism's counter, not on when it was published. Raise maxItems to cover more of the backlog if a delta run keeps returning fewer new records than expected. See AGENTS.md for the full live evidence.
eventTypesarray (enum: NEW_LISTING, STATUS_CHANGE, UPDATED)["NEW_LISTING", "STATUS_CHANGE", "UPDATED"]Which kinds of change to deliver when 'Only new records' is on (ignored, everything delivered, when it is off). NEW_LISTING = never seen before. STATUS_CHANGE = seen before, estado changed (e.g. Pendiente Analisis -> Adjudicado) - free to detect, no extra request. UPDATED = seen before, same estado, a field changed (monto, fecha, unidad ejecutora...).
resolveSourceUrlbooleantrueWhen enabled (default), resolves each delivered record's own permalink via one extra postback per row (~1.5s each - a real, measured cost, see AGENTS.md). Disable for a much faster run when you don't need a process-specific link: source_url falls back to the plain search page, and delivery is charged at the cheaper result-summary rate instead of result. See README 'How much does it cost'.
dateRangestring (enum: 24h, 7d, 30d)(none)Restricts the (raw, up-to-maxItems) results to processes whose own 'Fecha de apertura' (scheduled bid-opening date/time) falls within this window ending now. Independent of onlyNew. Note: Fecha de apertura is WHEN BIDS OPEN, not when the process was published on the portal - a currently-open tender is routinely dated in the future relative to when it first appeared, so this answers 'opens/opened in the last N', not 'listed in the last N'. A future-dated process never matches any window until its own opening date falls inside it. See README for detail.

Example configuration — plain pull

{
"maxItems": 100
}
{
"maxItems": 500,
"onlyNew": true,
"eventTypes": ["STATUS_CHANGE", "UPDATED"],
"resolveSourceUrl": true
}

Example configuration — large backlog sweep, fast and cheap

{
"maxItems": 1000,
"onlyNew": true,
"resolveSourceUrl": false,
"dateRange": "30d"
}

Output Dataset Sample & Data Dictionary

FieldTypeDescription
numeroProcesostringe.g. 10201-0001-CDI20
nombreProcesostringProcess name/subject
tipoProcesostringe.g. Contratacion Directa, Licitacion Publica
fechaAperturastringScheduled bid-opening date and time
estadostringProcess status, e.g. "Pendiente Análisis", "Adjudicado", "Desierto"
unidadEjecutorastringExecuting unit
servicioAdministrativoFinancierostringAdministrative/financial service
montostringBudget amount, raw source format (Argentine comma decimal)
record_idstringSame value as numeroProceso - stable across runs
event_typestringNEW_LISTING (never seen before), STATUS_CHANGE (estado changed since last seen), UPDATED (a field changed, same estado) or UNCHANGED (only when Only new records is off).
scraped_atstringISO-8601 timestamp of this extraction (same for every record in one run)
is_newbooleantrue if not seen in a prior run (delta mode)
contentHashstringsha1 fingerprint of this record's changeable fields - used to detect UPDATED between runs.
sourceUrlResolvedbooleanTrue when source_url is a genuine process-specific permalink; false when it's the generic search-page fallback.
source_urlstringDirect, cookie-independent link to the official process page (or the generic search page - see sourceUrlResolved)

Sample record

{
"numeroProceso": "10201-0034-CDI24",
"nombreProceso": "Adquisicion de insumos de bioseguridad para hospitales publicos",
"tipoProceso": "Contratacion Directa",
"fechaApertura": "12/11/2026 10:00 Hrs.",
"estado": "Adjudicado",
"unidadEjecutora": "Ministerio de Salud, Desarrollo Social y Deportes",
"servicioAdministrativoFinanciero": "SAF Central Salud",
"monto": "1869000,00",
"record_id": "10201-0034-CDI24",
"event_type": "STATUS_CHANGE",
"scraped_at": "2026-09-08T09:14:00.000Z",
"is_new": false,
"contentHash": "8f2a1c9e7b6d4a0f3e5c1b9d7a6f4e2c1b0a9d8e",
"sourceUrlResolved": true,
"source_url": "https://comprar.mendoza.gov.ar/PLIEGO/VistaPreviaPliegoCiudadano.aspx?qs=9f7a2e1c0d8b6a4f2e1c0d9b8a7f6e5d"
}

Multi-language Integration Snippets

cURL

curl "https://api.apify.com/v2/acts/stefano_seggio~mendoza-compras-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"maxItems": 500,
"onlyNew": true,
"eventTypes": ["STATUS_CHANGE", "UPDATED"],
"resolveSourceUrl": true
}'

Python (apify-client)

from apify_client import ApifyClient
client = ApifyClient(token="YOUR_APIFY_TOKEN")
run_input = {
"maxItems": 500,
"onlyNew": True,
"eventTypes": ["STATUS_CHANGE", "UPDATED"],
"resolveSourceUrl": True,
}
run = client.actor("stefano_seggio/mendoza-compras-monitor").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(f"[{item['event_type']}] {item['numeroProceso']} - {item['estado']} - {item['source_url']}")

Node.js (apify-client)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const runInput = {
maxItems: 500,
onlyNew: true,
eventTypes: ['STATUS_CHANGE', 'UPDATED'],
resolveSourceUrl: true,
};
const run = await client.actor('stefano_seggio/mendoza-compras-monitor').call(runInput);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
console.log(`[${item.event_type}] ${item.numeroProceso} - ${item.estado} - ${item.source_url}`);
}

Pricing Model Explanation

This actor bills per result event ("Pay per event"), with platform usage costs already included in each event's price:

EventPriceWhen it fires for this actor
result$0.003 per recordThe delivered record's source_url was genuinely resolved this run — i.e. resolveSourceUrl was true (the default) and the per-row detail postback succeeded.
result-summary$0.001 per recordEither resolveSourceUrl was set to false, or (with it left on) the per-row detail postback failed or the row's link markup was missing — in both cases source_url falls back to the generic search-page URL rather than a genuine permalink, and sourceUrlResolved is false.
apify-actor-start$0.00005Once per run, regardless of configuration.

The two-tier pricing exists specifically because of the one genuinely expensive step this actor performs: resolving a process-specific permalink for source_url costs one extra sequential postback per record (~1.5 seconds each), because Mendoza's results grid exposes only session-bound javascript:__doPostBack(...) links with no plain href to build a URL from. When that extra request happens and succeeds, the record is billed at the full result rate ($0.003) because it carries a genuine, stable, shareable link. When that step is skipped entirely (resolveSourceUrl: false) or it fails, the record still contains every domain field and every envelope field — it is simply missing a real permalink — and is billed at the lower result-summary rate ($0.001) to reflect that reduced value. This is not a "new vs. old record" distinction; it is a "was the one expensive lookup performed and did it succeed" distinction, and it applies identically to NEW_LISTING, STATUS_CHANGE, and UPDATED records alike.

For delta monitoring, enabling onlyNew: true means the actor still walks up to maxItems raw rows every run (this source's listing is not sorted newest-first, so there is no early-stop), but only rows found to be new, status-changed, or amended are actually pushed to the dataset — an already-seen, unchanged row is filtered out after being walked and is never created as a dataset item at all. It is not delivered and not billed at $0; it simply never becomes a chargeable event in the first place. This also means onlyNew reduces cost indirectly: rows filtered out by the seen-set never trigger the resolveSourceUrl detail postback either, so a warm delta run only pays the higher result rate for rows that genuinely changed.

Example run cost: A daily monitor that finds 5 changed processes with resolveSourceUrl: true costs about 5 × $0.003 + $0.00005 ≈ $0.015–0.02 per run (roughly $0.45–0.60/month at one run/day). A resolveSourceUrl: false sweep over a much larger maxItems window (e.g. covering more of the 25,784-process backlog) is both faster and cheaper per record, at the cost of every source_url being the generic search page rather than a process-specific link.