PBA Tenders Scraper & Monitor
Pricing
Pay per event
PBA Tenders Scraper & Monitor
Extracts public tender listings from PBAC, the official procurement portal of Buenos Aires Province, Argentina: upcoming openings, tenders from the last 30 days, and awarded processes, with optional full-detail enrichment.
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
PBA Tenders Scraper & Monitor — Apify Store Overview
Executive Summary & Business Use Case
PBA Tenders Scraper & Monitor extracts public tender listings from PBAC (pbac.cgp.gba.gov.ar), the official procurement portal of the Province of Buenos Aires, Argentina — the country's largest and most populous jurisdiction. The actor reads all three of PBAC's summary grids directly from the server-rendered homepage: upcoming openings (apertura_proxima), tenders from the last 30 days (ultimos_30_dias), and awarded processes (adjudicados), turning them into clean, structured records. No login and no browser are required — PBAC renders these tables as plain Bootstrap HTML on the first GET — and an optional full-detail mode can additionally simulate the site's per-process postback (see the Known Limitation note in the Technical Features section for exactly what that does and does not return today).
No Argentina national procurement actor covers this at the provincial level, and Buenos Aires Province runs its own separate portal from the national comprar.gob.ar system. That gap is the actor's core value: anyone who needs to track this specific province's public tenders has no other structured-data source to buy or build against. On top of the raw extraction, the actor ships a delta engine that reports only what genuinely changed between runs — a tender that is new, moved between lifecycle stages, was amended, or disappeared from the site entirely — so recurring monitoring doesn't require re-downloading and re-diffing the full grid yourself.
Three concrete, real-world use cases this data supports: (1) suppliers to Buenos Aires Province organisms (health authorities, general-services agencies, etc.) who need to know the moment a tracked tender moves to evaluation or gets awarded, so they can prepare or withdraw an offer at the right moment; (2) compliance and business-development teams who want a feed of newly published tenders (event_type=NEW_LISTING) filtered by organismo, so new opportunities get routed to the right owner without a person manually re-checking the portal; and (3) bid consultants and gestores managing several clients' tenders at once, who need a single feed of exactly what changed — event_type, previousVistaOrigen, previousEstado — since yesterday, across every tender they track, so they can notify each client with the specific change rather than a full re-read of the site.
Technical Features & V2 Architecture Highlights
Delta engine with cross-run persistence. This is version 2.0 of the actor, and per its own CHANGELOG.md it is "the first delta engine this actor has ever had, built directly to this fleet's v2 standard" — unlike sibling actors in the same portfolio that went through a v1-then-v2 retrofit, PBA Tenders Monitor shipped with STATUS_CHANGE/UPDATED/CLOSED detection from day one. State is tracked in a named key-value store scoped to this actor (per the onlyNew input field's own description), so a scheduled run can compare against what a prior run last observed without you managing any external database.
Real, domain-specific event types — not a generic set. The actor's event_type field (confirmed in .actor/dataset_schema.json) uses exactly five values: NEW_LISTING (never seen before), STATUS_CHANGE (the tender's vistaOrigen — which of the three PBAC grids it's in — or its estado field changed since it was last seen), UPDATED (same grid and status, but some other field changed, detected via a sha1 content fingerprint stored in contentHash), UNCHANGED (delivered only when onlyNew is off), and CLOSED (no longer listed in any of the three grids). STATUS_CHANGE is worth calling out specifically: because a tender moving from "upcoming openings" to "awarded" is a real lifecycle transition that both grids already expose for free, the actor tracks vistaOrigen exactly like estado and reports whichever one actually changed in previousVistaOrigen/previousEstado.
onlyNew (delta mode). Per the actor's own input schema, enabling onlyNew means recurring/scheduled runs "return only tenders that are new, changed vistaOrigen/estado, amended or closed since a prior run of this actor." When onlyNew is off, every tender in the requested views is returned every run, including ones that are UNCHANGED. The companion eventTypes input then narrows which of the four delta categories (NEW_LISTING/STATUS_CHANGE/UPDATED/CLOSED) get delivered when delta mode is on; it's ignored entirely when onlyNew is off.
CLOSED has a real, disclosed gating condition. CLOSED — a tender absent from all three grids — is only ever computed when the views input covers all three default views. Requesting a narrower selection turns the run's fetch into a genuine subset of the site, so a previously tracked tender missing from a partial fetch might simply live in a grid you didn't request rather than actually be gone; in that case CLOSED is skipped and logged rather than guessed at. Separately, ultimos_30_dias is a genuinely rolling 30-day window, so a tender can age out of that one grid purely because time passed — CLOSED only ever means "absent from all three grids," never "removed from one specific view."
Output schema field count. .actor/dataset_schema.json defines 16 fields per record: 9 domain fields describing the tender itself (numeroProceso, descripcion, tipoProcedimiento, fechaApertura, estado, organismo, vistaOrigen, detalleCompleto, scrapedAt) plus the 7-field standardized envelope the v2 changelog introduced (record_id, event_type, previousVistaOrigen, previousEstado, is_new, source_url, contentHash). is_new is always populated — even on a plain non-delta run — as true when the record's id wasn't in the persisted seen-set at the start of the run.
Required residential proxy. PBAC blocks non-residential-Argentina traffic — verified live: both no proxy and a standard datacenter proxy time out identically on every request, while Residential proxy with country set to Argentina succeeds consistently. The actor's proxyConfiguration input defaults to { useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"], apifyProxyCountry: "AR" } and this should not be switched to datacenter.
Input Schema & JSON Configuration Example
| Field | Type | Default | Description |
|---|---|---|---|
views | array (enum: apertura_proxima, ultimos_30_dias, adjudicados) | all three | Which PBAC grids to extract. Defaults to all three. |
fetchFullDetail | boolean | false | When enabled, simulates the postback for each process. Known limitation: this currently returns only a short status label, not the full Pliego document text - that content lives in a nested panel not yet mapped. Leave off until this is resolved; it adds cost with limited extra value today. |
maxItems | integer | 200 | Hard cap on the number of tender processes returned this run, across all selected views. |
proxyConfiguration | object | Residential + Argentina | Required: pbac.cgp.gba.gov.ar blocks non-residential-Argentina traffic. Verified live - datacenter proxy (any provider) times out identically to no proxy at all; only Residential + Argentina succeeds. Defaults to that; do not switch to datacenter. |
onlyNew | boolean | false | For recurring/scheduled runs: return only tenders that are new, changed vistaOrigen/estado, amended or closed since a prior run of this actor (tracked in a named key-value store scoped to this actor). CLOSED (a tender no longer in any of the 3 grids) is only ever computed when 'Views to extract' covers all 3 default views - a narrower selection makes this run's fetch a subset of the site, so CLOSED is skipped (and logged) in that case. |
eventTypes | array (enum: NEW_LISTING, STATUS_CHANGE, UPDATED, CLOSED) | all four | 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 = vistaOrigen or estado changed - e.g. a tender moving from upcoming to awarded. UPDATED = same vistaOrigen/estado, a field changed. CLOSED = no longer listed in any of the 3 grids (only computed on an unfiltered 'Views to extract'). |
dateRange | string (enum: 24h, 7d, 30d) | (none) | Restricts results to tenders whose fechaApertura falls within this window ending now. Independent of onlyNew. Note: fechaApertura is WHEN BIDS OPEN, not when the tender was published - for the 'Upcoming openings' view specifically that date is routinely in the future, so this filter will not match those rows (see AGENTS.md/README). |
A valid configuration for a daily monitoring run, watching all three grids and receiving only what changed since the previous run:
{"views": ["apertura_proxima", "ultimos_30_dias", "adjudicados"],"fetchFullDetail": false,"maxItems": 200,"onlyNew": true,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE", "UPDATED", "CLOSED"],"dateRange": "30d","proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"],"apifyProxyCountry": "AR"}}
A minimal configuration for a one-off pull of just the two forward-looking grids, no delta tracking:
{"views": ["apertura_proxima", "ultimos_30_dias"],"maxItems": 100}
Output Dataset Sample & Data Dictionary
| Field | Type | Description |
|---|---|---|
numeroProceso | string | PBAC process number, e.g. 2026-338-99-265 |
descripcion | string | Short description of the tender |
tipoProcedimiento | string | Licitacion Publica, Licitacion Privada, or Procedimiento Abreviado |
fechaApertura | string | Opening date and time as shown by PBAC |
estado | string | Publicado, En Apertura, En Evaluacion, Disponible Para Adjudicar, etc. |
organismo | string | Contracting unit/agency |
vistaOrigen | string | Which PBAC grid this row came from: apertura_proxima, ultimos_30_dias or adjudicados |
detalleCompleto | object | null | Full document preview text, only populated when fetchFullDetail is enabled |
scrapedAt | string | ISO timestamp of extraction |
record_id | string | Same value as numeroProceso |
event_type | string | NEW_LISTING (never seen before), STATUS_CHANGE (vistaOrigen or estado changed), UPDATED (a field changed), UNCHANGED (only when Only new records is off) or CLOSED (no longer in any of the 3 grids). |
previousVistaOrigen | string | null | Set only when event_type=STATUS_CHANGE and vistaOrigen changed. |
previousEstado | string | null | Set only when event_type=STATUS_CHANGE and estado changed. |
is_new | boolean | True if this id was not in the persisted seen-set when this run started. |
source_url | string | The PBAC homepage - this portal has no per-tender deep link. |
contentHash | string | sha1 fingerprint used to detect UPDATED between runs. |
A realistic single dataset record, as delivered by a delta-mode run for a tender that just moved from "upcoming openings" to a new status:
{"numeroProceso": "2026-338-99-265","descripcion": "Adquisicion de insumos de bioseguridad para hospitales de la Region Sanitaria VI","tipoProcedimiento": "Licitacion Publica","fechaApertura": "15/10/2026 10:00","estado": "En Evaluacion","organismo": "Ministerio de Salud de la Provincia de Buenos Aires","vistaOrigen": "ultimos_30_dias","detalleCompleto": null,"scrapedAt": "2026-09-08T14:32:07.118Z","record_id": "2026-338-99-265","event_type": "STATUS_CHANGE","previousVistaOrigen": null,"previousEstado": "Publicado","is_new": false,"source_url": "https://pbac.cgp.gba.gov.ar/","contentHash": "3f9a2b7c4d1e8f0561a29b3c7d4e8f9012ab34cd"}
Multi-language Integration Snippets
cURL
curl "https://api.apify.com/v2/acts/stefano_seggio~pba-tenders-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-X POST \-H "Content-Type: application/json" \-d '{"views": ["apertura_proxima", "ultimos_30_dias", "adjudicados"],"onlyNew": true,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE", "UPDATED", "CLOSED"],"maxItems": 200}'
Python (apify-client)
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run_input = {"views": ["apertura_proxima", "ultimos_30_dias", "adjudicados"],"onlyNew": True,"eventTypes": ["NEW_LISTING", "STATUS_CHANGE", "UPDATED", "CLOSED"],"maxItems": 200,}run = client.actor("stefano_seggio/pba-tenders-monitor").call(run_input=run_input)for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(f"[{item['event_type']}] {item['numeroProceso']} - {item['organismo']}")
Node.js (apify-client)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('stefano_seggio/pba-tenders-monitor').call({views: ['apertura_proxima', 'ultimos_30_dias', 'adjudicados'],onlyNew: true,eventTypes: ['NEW_LISTING', 'STATUS_CHANGE', 'UPDATED', 'CLOSED'],maxItems: 200,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const item of items) {console.log(`[${item.event_type}] ${item.numeroProceso} - ${item.organismo}`);}
Pricing Model Explanation
This actor bills per event, on top of standard Apify platform usage:
| Event | Price | When it fires on this actor |
|---|---|---|
result | $0.003 per record | Every NEW_LISTING, STATUS_CHANGE, or UPDATED record — a tender whose full row content was actually parsed from a PBAC grid this run. |
result-summary | $0.001 per record | Every CLOSED record — a derived absence signal (the tender is inferred no longer listed in any of the 3 grids); nothing new was fetched for it, so it bills at the lower tier. |
apify-actor-start | $0.00005 | Once per run, regardless of how many tenders are found. |
The two-tier split is about what was actually fetched, not about fetchFullDetail. result and result-summary are priced differently because a CLOSED record reflects an inference from what's already been scraped in a run (the tender simply isn't in any grid anymore) rather than new content being pulled from the site, while NEW_LISTING/STATUS_CHANGE/UPDATED all correspond to a genuinely parsed row. Enabling fetchFullDetail does not move a record into a different pricing tier: today it only returns a short status label rather than the full Pliego document text (a known, disclosed limitation — see the Technical Features section), and charging more for a fetch that isn't yet meaningfully richer would be misleading, so result stays $0.003 whether or not fetchFullDetail is on.
If you turn on onlyNew (delta mode), an unchanged tender is simply never turned into a dataset record at all — it is not delivered and not billed at $0, it just never becomes a chargeable event in the first place. Only tenders whose event type is NEW_LISTING, STATUS_CHANGE, UPDATED, or CLOSED (and is included in your eventTypes selection) generate a billable event. A daily monitor that finds 5 changes across all 3 grids costs about $0.02/day (roughly $0.45/month) plus the fixed $0.00005 actor-start charge per run.