Entre Rios Tenders Scraper & Monitor
Pricing
Pay per event
Entre Rios Tenders Scraper & Monitor
Extracts public tenders and contracting processes from the Province of Entre Rios, Argentina Unidad Central de Contrataciones, covering a large multi-year backlog with organism and objeto detail.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Stefano Seggio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
15 hours ago
Last modified
Categories
Share
Entre Rios Tenders Scraper & Monitor — Apify Store Overview
Actor ID: oiXeFzZGlIQ6mKgoo · Store URL: https://apify.com/stefano_seggio/entrerios-compras-monitor · Version: 2.0
Executive Summary & Business Use Case
Entre Rios Tenders Scraper & Monitor extracts every public tender and contracting process published by the Province of Entre Rios, Argentina's Unidad Central de Contrataciones, from its live tender listing at entrerios.gov.ar/contrataciones/licitaciones.php. In a single unfiltered request it returns the entire multi-year backlog — 5,505 rows verified live, spanning 2004 through 2025 — with the issuing organism, the object of the procurement, the destination office, the procedure type, and the current status (estado) for every process. There is no official change-tracking feed or API on the Entre Rios side; this actor is the structured, monitorable layer the province never built.
The data supports a narrow but real set of business use cases, all grounded in what the source actually contains: a status field, an issuing organism, and a free-text description of what is being procured — nothing about monetary value, since the source publishes none. Suppliers to provincial organisms (health, education, the judicial branch, roads) use it to track whether a tender they're bidding on has moved to "Realizada" (completed) or "Fracasada" (failed/void), so they stop chasing dead processes and re-check the ones that changed. Bid consultants and gestores managing multiple clients' tender pipelines use the delta feed to see exactly what changed since yesterday across every tracked process, so they can notify each client with the specific change rather than re-reading the whole listing. Journalists, researchers, and transparency groups use the organismo, estado, and tipoLicitacion fields across the full 20-year backlog to analyze which provincial bodies run the most contracting processes and how often they complete versus fail.
What this actor is deliberately not positioned for: spend analysis by contract value, since Entre Rios' own listing has no monetary field to extract; and precise multi-day recency filtering, since the source publishes no per-record date anywhere on the listing or on any detail page (there is no detail page — every record shares the same source_url).
Technical Features & V2 Architecture Highlights
Cross-run delta persistence via a named key-value store. The actor's delta engine does not rely on the ephemeral, per-run default key-value store. src/state.ts opens a fixed, actor-scoped named store (entrerios-compras-monitor-delta-state) via Actor.openKeyValueStore, which persists across runs and across a scheduled task's own run retention — the default store (Actor.setValue/Actor.getValue with no store name) is wiped and replaced on every run and cannot carry state between them. The persisted state shape is { entries: Record<record_id, { estado }>, lastRunAt } — each seen record_id is stored with the estado it was last observed under, which is what makes free STATUS_CHANGE detection possible without a second fetch.
Real event types, not the generic fleet default. This actor's event_type field takes exactly four values, defined in its own dataset_schema.json and README.md: NEW_LISTING (record_id never seen before), STATUS_CHANGE (record_id seen before, estado differs from what was last stored — set alongside previousEstado), UNCHANGED (seen before, estado identical — emitted only when onlyNew is off, since delta mode filters these out), and CLOSED (a previously-seen record_id is absent from this run's fetch). There is deliberately no UPDATED event: record_id is itself a stable hash of procedimiento+objeto+destino+organismo, so any change to those fields produces a genuinely different hash — indistinguishable from a brand-new listing without a real source-issued row id to correlate the old and new records against. estado is the only field this source can ever truthfully report as "the same record changed."
CLOSED has a correctness gate, not a blanket rule. It is only computed on an unfiltered run — no estado/tipoLicitacion/organismo/anio/palabra set — because a filtered run's fetch is a subset of the register, not the whole thing, so a previously-seen tender missing from a filtered fetch might simply be outside this run's filter rather than actually gone. This gate exists because live verification caught the failure mode directly: a filtered follow-up run wrongly flagged 37 unrelated records as CLOSED before the fix shipped. On a filtered run, CLOSED detection is skipped and logged rather than silently producing false positives.
What onlyNew actually does here, per the actor's own input schema. Per its real description in input_schema.json: for recurring/scheduled runs, setting onlyNew: true returns only tenders that are new, changed estado, or closed since a prior run — tracked in the named key-value store above. Because this source has no genuine pagination (a single blank-estado POST returns the entire ~5,505-row backlog every time, oldest-first by year, not newest-first), enabling onlyNew does not reduce fetch time or request volume: the full backlog is fetched every run regardless, and the flag only changes which records get pushed to the dataset afterward.
is_new is always computed. Independent of onlyNew, every record carries is_new: true if its record_id was not already in the persisted seen-set when the run started — this is set correctly even on a plain, non-delta run.
No dateRange effect, disclosed rather than silently ignored. dateRange exists in the input schema purely for shape-consistency with the rest of this actor's monitor-actor portfolio. This specific source publishes no per-record date field anywhere — not on the listing, not on any detail page, because no detail page exists. Setting dateRange has no effect on results; the actor logs a warning instead of silently applying a filter that would mislead the caller.
Output shape. The dataset schema (dataset_schema.json) defines 12 fields per record: record_id, procedimiento, anioProcedimiento, objeto, destino, organismo, estado, event_type, previousEstado, scraped_at, is_new, source_url. Two dataset views ship with the actor — "Overview" (the listing-style fields) and "Status changes & closures" (the delta-relevant fields: record_id, event_type, previousEstado, estado, organismo, objeto, scraped_at).
Input Schema & JSON Configuration Example
| Field | Type | Default | Description |
|---|---|---|---|
estado | string (enum) | "" | Filter by process status. Leave blank (default) to pull every status in one request - the site's own union of all 4, verified live to be far larger than any single status alone. Values: "" All statuses, "1" Proxima Apertura (upcoming opening), "2" En proceso de Evaluacion (under evaluation), "3" Realizada (completed), "4" Fracasada (failed/void). |
tipoLicitacion | string (enum) | "" | Filter by contracting procedure type. Leave blank for all types. Values: "" All types, "1" Licitacion Privada, "2" Licitacion Publica, "3" Solicitud de Cotizacion, "4" Contratacion Directa. |
organismo | string (enum) | "" | Filter by issuing organism. Leave blank for all organisms. Values: "" All organisms, "1" Unidad Central de Contrataciones, "3" Ministerio de Desarrollo Social, "4" Consejo General de Educacion, "5" Administradora Tributaria de Entre Rios, "6" UADER, "7" Oficina de Compras y Asesoramiento - Poder Judicial de Entre Rios, "8" Ministerio de Salud, "9" Direccion Provincial de Vialidad. |
anio | string | "" | Filter by year, e.g. "2025". Leave blank for all years (data goes back to at least 2004). |
palabra | string | "" | Free-text keyword filter, matched server-side the same way the site's own "Palabra" search box works. |
maxItems | integer | 1000 | Hard cap on the number of tenders returned this run. The full unfiltered backlog is over 5000 rows (verified live) - narrow with the filters above or raise this cap to pull all of it. |
onlyNew | boolean | false | For recurring/scheduled runs: return only tenders that are new, changed estado, or closed since a prior run of this actor (tracked in a named key-value store scoped to this actor, so it survives between scheduled runs). This source is fetched as a single unfiltered POST covering the entire backlog rather than genuine, reliably newest-first pagination (verified live - see AGENTS.md), so enabling this does NOT reduce fetch time or request volume: the full backlog is still fetched every run either way, and this only changes which records get pushed to the dataset afterward. |
eventTypes | array (enum items) | ["NEW_LISTING","STATUS_CHANGE","CLOSED"] | 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 - free to detect, record_id's own hash excludes estado on purpose. CLOSED = a previously-seen record_id is absent from this run's fetch - only computed on an UNFILTERED run (no status/type/organism/year/keyword filter set), since a filtered run's fetch is a subset of the register, not the whole thing; skipped otherwise. |
dateRange | string (enum) | "" | Filters to a source's own natural per-record date field, where one of this portfolio's monitor actors has one. This source (Entre Rios' tender listing) publishes NO per-record date field anywhere - not on the listing, not on any detail page (there isn't one) - verified live against the real page (see AGENTS.md). Setting this has NO effect on the results returned; the actor logs a warning instead of silently applying a misleading filter. Present only for input-shape consistency with the rest of the portfolio. |
Example configuration — scheduled delta monitor for one organism
{"estado": "","tipoLicitacion": "","organismo": "8","anio": "","palabra": "","maxItems": 6000,"onlyNew": true,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE"],"dateRange": ""}
This configuration leaves every content filter blank except organismo (so it still tracks Ministerio de Salud specifically), raises maxItems above the current backlog size, enables delta mode, and asks only for new listings and status changes (not closures — note that setting organismo here means CLOSED would be skipped anyway, since CLOSED requires a fully unfiltered run).
Example configuration — one-off full backlog pull, no filters
{"estado": "","tipoLicitacion": "","organismo": "","anio": "","palabra": "","maxItems": 6000,"onlyNew": false,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE", "CLOSED"],"dateRange": ""}
Output Dataset Sample & Data Dictionary
| Field | Type | Description |
|---|---|---|
record_id | string | Stable hash of procedimiento+objeto+destino+organismo - the site has no native row id. Deliberately excludes estado, so the same procedure keeps this id across status changes. |
procedimiento | string | Raw procedure string as published by the source. |
anioProcedimiento | string | null | Year field associated with the procedure. |
objeto | string | What is being procured. |
destino | string | Destination office/department. |
organismo | string | Issuing organism. |
estado | string | Current status of the process. |
event_type | string | NEW_LISTING (never seen before), STATUS_CHANGE (estado changed since last seen), UNCHANGED (only when Only new records is off) or CLOSED (no longer in the register). |
previousEstado | string | null | Set only when event_type=STATUS_CHANGE: the estado this record_id was last seen under. |
scraped_at | string | ISO timestamp of this run's extraction (same value for every record from one run). |
is_new | boolean | True if record_id was not in the persisted seen-set when this run started. |
source_url | string | The shared search-listing page - this source has no per-tender detail link. |
Example dataset record
{"record_id": "a1e4f9c2d7b6803e5f18c4a9b2d6e701","procedimiento": "Solicitud De Cotizacion 54/2025","anioProcedimiento": "2025","objeto": "Adquisicion de insumos descartables para centros de salud del interior provincial","destino": "Direccion de Suministros - Ministerio de Salud","organismo": "Ministerio de Salud","estado": "Realizada","event_type": "STATUS_CHANGE","previousEstado": "En proceso de Evaluación","scraped_at": "2026-09-08T09:15:42.118Z","is_new": false,"source_url": "https://www.entrerios.gov.ar/contrataciones/licitaciones.php"}
Multi-language Integration Snippets
cURL
curl "https://api.apify.com/v2/acts/stefano_seggio~entrerios-compras-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"organismo": "8","onlyNew": true,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE"],"maxItems": 2000}'
Python (apify-client)
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run_input = {"organismo": "8","onlyNew": True,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE"],"maxItems": 2000,}run = client.actor("stefano_seggio/entrerios-compras-monitor").call(run_input=run_input)for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["record_id"], item["event_type"], item["estado"], item["organismo"])
Node.js (apify-client)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const runInput = {organismo: '8',onlyNew: true,eventTypes: ['NEW_LISTING', 'STATUS_CHANGE'],maxItems: 2000,};const run = await client.actor('stefano_seggio/entrerios-compras-monitor').call(runInput);const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const item of items) {console.log(item.record_id, item.event_type, item.estado, item.organismo);}
Pricing Model Explanation
This actor bills on two named pay-per-event types, live-verified on the Apify platform:
| Event | Price | Meaning for this actor |
|---|---|---|
result | $0.003 per record | Charged once for every record delivered into the dataset. There is deliberately a single tier here, with no lower-priced result-summary variant: this source has no detail-page split to exploit for a two-tier price — every fetched row already carries the same complete, final content (procedimiento, objeto, destino, organismo, estado) at identical extraction cost, because there is no per-tender detail page to optionally skip. |
apify-actor-start | $0.00005 | Charged once per run, regardless of how many records that run delivers. |
How onlyNew interacts with billing. When onlyNew: true, the actor still fetches the entire ~5,505-row backlog on every run (there is no pagination to short-circuit), but only pushes NEW_LISTING, STATUS_CHANGE, or CLOSED records — restricted further by eventTypes — into the dataset. An unchanged record is filtered out before it ever becomes a dataset item: it is never delivered and never billed as a result event. It is not billed at $0; it simply never becomes a chargeable event at all, because the delta engine never writes it to the dataset.
Cost examples, grounded in this actor's own verified backlog size:
- A one-off unfiltered pull of the full backlog (5,505 rows) costs approximately 5,505 × $0.003 + $0.00005 ≈ $16.52.
- A daily scheduled
onlyNew: truemonitor that typically surfaces around 10 changed records a day costs approximately 10 × $0.003 + $0.00005 ≈ $0.03/day (roughly $0.90/month) — the full backlog fetch happens every run regardless, but only the genuinely new, status-changed, or closed records are ever written to the dataset and billed.