# Cordoba Tenders Scraper & Monitor (`stefano_seggio/cordoba-compras-monitor`) Actor

Extracts active public tenders (licitaciones) from the Province of Cordoba, Argentina official procurement portal, with organism, dates, status, reference budget per item and contact phone.

- **URL**: https://apify.com/stefano\_seggio/cordoba-compras-monitor.md
- **Developed by:** [Stefano Seggio](https://apify.com/stefano_seggio) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Cordoba Tenders Scraper & Monitor — Apify Store Overview

**Actor:** `stefano_seggio/cordoba-compras-monitor`
**Actor ID:** `q9jhMgJRSGjyNbXKA`
**Store URL:** https://apify.com/stefano\_seggio/cordoba-compras-monitor
**Version:** 2.0

***

### Executive Summary & Business Use Case

Cordoba Tenders Scraper & Monitor extracts every active public tender (*licitacion*) from the Province of Cordoba, Argentina's official e-procurement portal (`webecommerce.cba.gov.ar`), the government system where the province publishes its open contracting opportunities. For each tender the actor returns the process number, contracting type, issuing agency, jurisdiction, publication and closing dates, current status, whether the deadline has been extended, the full line-item list with quantity and reference/official budget per item, and the published contact phone — all pulled from a single request per listing page, with no secondary "detail page" fetch required. On top of the raw extraction, the actor ships a delta engine: run it on a schedule with delta mode on and it reports only what actually changed since the previous run — a brand-new tender, a status change, a content amendment (e.g. a deadline extension or a revised budget line), or a tender that has dropped off the active register entirely.

This is a genuinely difficult portal to work with in practice — the source is a stateful ASP.NET postback/ViewState session with a sliding pager window, it serves an incomplete TLS certificate chain that has to be explicitly patched around, and it blocks any non-Argentina, non-residential IP outright. All of that plumbing is solved inside the actor, so the integrator receives a plain structured feed and never has to touch a browser session, a ViewState token, or a proxy configuration edge case.

Three concrete buyers for this data, grounded directly in what the source actually contains: **suppliers who sell to Cordoba provincial agencies** use it to watch a shortlist of tenders and get notified the moment one is awarded/closed or its deadline is extended, so they stop chasing dead opportunities and never miss a live one; **bid consultants and *gestores*** who manage several clients' tender pipelines use the per-run `event_type` and `previousEstado` fields to know exactly what changed for which client since the last check, without manually re-reading the portal; and **journalists, researchers and transparency/procurement-analytics groups** use `servicioAdministrativo` (issuing agency), `tipoContratacion` and the per-item `presupuestoOficial` (official reference budget) to analyze which agencies run the most tenders and at what reference-budget scale. A monetary "deal value" use case beyond the published per-item reference/official budget is not supported — the portal does not publish an awarded contract value, only the pre-award reference budget per line item.

### Technical Features & V2 Architecture Highlights

**Cross-run delta persistence.** When `onlyNew` is enabled, the actor persists which tender ids (`nroCotizacion`) it has already seen — together with each one's last-known `estado` and a content fingerprint — in its own named key-value store that survives between scheduled runs. Each new run diffs the freshly walked listing against that persisted state to decide, per tender, whether it is genuinely new, changed status, amended, or closed.

**Real event types (source-specific, not the generic fleet default).** This actor's `event_type` field takes one of five values, confirmed directly in `dataset_schema.json`: `NEW_LISTING` (never seen before), `STATUS_CHANGE` (seen before, `estado` changed — free to detect since `estado` is already in every walked row), `UPDATED` (seen before, same `estado`, but some other field changed — e.g. a `prorroga` deadline extension or an amended line-item budget, detected via the sha1 `contentHash` fingerprint), `UNCHANGED` (only ever emitted when delta mode is off, i.e. a full non-delta run), and `CLOSED` (a previously-tracked tender is absent from a complete walk of the active register). Unlike some sibling actors in this fleet, this schema includes all four change categories plus `CLOSED`; there is no domain-specific event like `SANCTION` here because Cordoba's portal has no such concept — the four/five-way split above is the complete real set for this actor.

**What `onlyNew` actually does, per its own schema description.** `onlyNew`'s `input_schema.json` description states plainly: when enabled, the actor "persists which tender ids (nroCotizacion) this actor has already returned — and their last-known estado + content fingerprint — in its own named key-value store that survives between scheduled runs, and returns only tenders that are new, changed estado, amended, or closed since a prior run." Critically, this actor's implementation of `onlyNew` deliberately does **not** stop pagination early the way some of this fleet's other delta-enabled monitors do. The schema and README both document why: Cordoba's listing is not reliably sorted newest-first end to end — a real, live-verified tender (`2026/000033`) was found sitting out of both `fechaInicio` order and `nroCotizacion` order within the very first page, both in a captured fixture and in a fresh live re-pull. Short-circuiting pagination on a source like that risks silently missing a genuinely new tender. So `onlyNew` here fetches up to `maxItems` in full, exactly like a normal run, and only filters the complete result afterward — a correctness-first design choice, explicitly not a pagination-cost optimization.

**`CLOSED` requires a complete walk.** A previously-tracked tender absent from this run's fetch is only reported `CLOSED` when the walk was not truncated by `maxItems` — a partial walk cannot prove a tender is actually gone rather than simply past where the walk stopped. For a recurring monitor that needs reliable `CLOSED` detection, `maxItems` should be set above the real count of active tenders.

**Output envelope field count.** The dataset schema defines 17 top-level record fields: 10 domain fields (`nroCotizacion`, `tipoContratacion`, `servicioAdministrativo`, `jurisdiccion`, `fechaInicio`, `fechaFinalizacion`, `estado`, `prorroga`, `items`, `telefonoContacto`) plus 7 standardized integration/envelope fields (`record_id`, `event_type`, `previousEstado`, `contentHash`, `scraped_at`, `is_new`, `source_url`) shared with this actor's sibling actors in the same portfolio. No generic "18-field" figure is claimed here — this actor's own `dataset_schema.json` confirms exactly 17 top-level fields, and that is the number worth quoting.

### Input Schema & JSON Configuration Example

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `maxItems` | integer | `200` | Hard cap on the number of active tenders (licitaciones) returned this run. The source only covers active/open tenders (Licitaciones) - awarded or historical tenders are out of scope for this actor. |
| `proxyConfiguration` | object | `{ "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "AR" }` | Required: webecommerce.cba.gov.ar blocks non-residential-Argentina traffic from the cloud (verified live - ConnectTimeoutError from Apify's datacenter IPs). Defaults to Residential + Argentina; do not switch to datacenter. |
| `onlyNew` | boolean | `false` | When enabled, persists which tender ids (nroCotizacion) this actor has already returned - and their last-known estado + content fingerprint - in its own named key-value store that survives between scheduled runs, and returns only tenders that are new, changed estado, amended, or closed since a prior run. IMPORTANT: this source's listing is NOT reliably sorted newest-first end to end (verified live - a real tender can appear out of order relative to both its own Fecha Inicio and its own nroCotizacion, within the very first page), so onlyNew filters AFTER a full fetch (up to maxItems) rather than stopping pagination early like this fleet's other delta-enabled actors. It is correct, just not a pagination-cost optimization. Independent of dateRange. Recommended for recurring monitoring; leave off for a full one-off extraction. |
| `eventTypes` | array | `["NEW_LISTING", "STATUS_CHANGE", "UPDATED", "CLOSED"]` | Which kinds of change to deliver when 'Delta mode' is on (ignored, everything delivered, when it is off). NEW\_LISTING = never seen before. STATUS\_CHANGE = seen before, estado changed - free to detect. UPDATED = seen before, same estado, a field changed (a prorroga granted, an amended item/budget). CLOSED = a previously-active tender is absent from this run's fetch - only computed when maxItems does not truncate the walk (a partial walk cannot prove absence). |
| `dateRange` | string | (none) | Optionally restrict results to tenders whose Fecha Inicio (publication date) falls within this window. Independent of onlyNew - this is the source's own declared publication timestamp, not independently cross-verified against when a tender actually first appeared on the portal. Enum: `"24h"`, `"7d"`, `"30d"`. |

**JSON configuration example** (a recurring delta-monitoring setup, narrowed to only new tenders and status changes, filtered to the last 7 days):

```json
{
  "maxItems": 300,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "AR"
  },
  "onlyNew": true,
  "eventTypes": ["NEW_LISTING", "STATUS_CHANGE"],
  "dateRange": "7d"
}
```

A minimal, valid full-extraction example (all fields except `maxItems` fall back to their schema defaults):

```json
{ "maxItems": 200 }
```

### Output Dataset Sample & Data Dictionary

| Field | Type | Description |
| --- | --- | --- |
| `nroCotizacion` | string | Tender process number, e.g. `2026/000091` |
| `tipoContratacion` | string | Contract type |
| `servicioAdministrativo` | string | Issuing agency |
| `jurisdiccion` | string | Jurisdiction |
| `fechaInicio` | string | Publication date |
| `fechaFinalizacion` | string | Closing/opening date and time |
| `estado` | string | Status |
| `prorroga` | boolean | Whether the deadline has been extended |
| `items` | array of objects | Line items with quantity and reference/official budget, extracted inline - no extra request needed. Each item: `renglon` (string), `cantidad` (string), `precioReferencia` (string), `presupuestoOficial` (string) |
| `telefonoContacto` | string | null | Contact phone |
| `record_id` | string | Same value as `nroCotizacion` - the natural unique id for this tender |
| `event_type` | string | `NEW_LISTING` (never seen before), `STATUS_CHANGE` (estado changed since last seen), `UPDATED` (a field changed, same estado), `UNCHANGED` (only when Delta mode is off) or `CLOSED` (no longer active) |
| `previousEstado` | string | null | Set only when event\_type=STATUS\_CHANGE: the estado this record\_id was last seen under |
| `scraped_at` | string | ISO-8601 timestamp of this run's extraction, same value for every record from one run |
| `is_new` | boolean | True if this tender's id was not in the persisted seen-set when this run started |
| `contentHash` | string | sha1 fingerprint of this record's changeable fields - used to detect UPDATED between runs |
| `source_url` | string | The general listing page - this portal has no stable per-tender deep link (session/ViewState-bound postback UI) |

**Sample dataset record:**

```json
{
  "nroCotizacion": "2026/000142",
  "tipoContratacion": "Licitacion - Soporte Digital",
  "servicioAdministrativo": "Ministerio de Salud",
  "jurisdiccion": "Provincia de Cordoba",
  "fechaInicio": "01/09/2026 10:15",
  "fechaFinalizacion": "22/09/2026 12:00",
  "estado": "EN PROCESO",
  "prorroga": false,
  "items": [
    {
      "renglon": "1",
      "cantidad": "500",
      "precioReferencia": "1250.00",
      "presupuestoOficial": "625000.00"
    },
    {
      "renglon": "2",
      "cantidad": "120",
      "precioReferencia": "3400.00",
      "presupuestoOficial": "408000.00"
    }
  ],
  "telefonoContacto": "0351-4341300",
  "record_id": "2026/000142",
  "event_type": "NEW_LISTING",
  "previousEstado": null,
  "scraped_at": "2026-09-08T14:32:07.000Z",
  "is_new": true,
  "contentHash": "8f2a6c1e0d4b7a9c3e5f1a2b4c6d8e0f12345678",
  "source_url": "https://webecommerce.cba.gov.ar/Compras/Compras.aspx"
}
```

### Multi-language Integration Snippets

#### cURL

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

#### Python (apify-client)

```python
from apify_client import ApifyClient

client = ApifyClient(token="YOUR_APIFY_TOKEN")

run_input = {
    "maxItems": 300,
    "onlyNew": True,
    "eventTypes": ["NEW_LISTING", "STATUS_CHANGE", "UPDATED", "CLOSED"],
    "dateRange": "7d",
}

run = client.actor("stefano_seggio/cordoba-compras-monitor").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"[{item['event_type']}] {item['nroCotizacion']} - {item['servicioAdministrativo']} - {item['estado']}")
```

#### Node.js (apify-client)

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const runInput = {
    maxItems: 300,
    onlyNew: true,
    eventTypes: ['NEW_LISTING', 'STATUS_CHANGE', 'UPDATED', 'CLOSED'],
    dateRange: '7d',
};

const run = await client.actor('stefano_seggio/cordoba-compras-monitor').call(runInput);
const { items } = await client.dataset(run.defaultDatasetId).listItems();

for (const item of items) {
    console.log(`[${item.event_type}] ${item.nroCotizacion} - ${item.servicioAdministrativo} - ${item.estado}`);
}
```

*(Equivalent direct API call for all three: `POST https://api.apify.com/v2/acts/stefano_seggio~cordoba-compras-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN`)*

### Pricing Model Explanation

This actor uses Apify's Pay-Per-Event model with three named events:

| Event | Price | When it fires, for this actor |
| --- | --- | --- |
| `result` | **$0.003** per record | Charged for `NEW_LISTING`, `STATUS_CHANGE`, or `UPDATED` records — every one of these already carries the full tender detail (organism, dates, status, per-item budgets, contact phone) inline, pulled from the listing walk with no extra request needed. |
| `result-summary` | **$0.001** per record | Charged only for `CLOSED` records — a `CLOSED` event is a derived absence signal (a previously-tracked tender no longer appears in a complete walk), so there is no fresh tender content to fetch or deliver, and it bills at the lower tier accordingly. |
| `apify-actor-start` | **$0.00005** | Charged once per run, regardless of how many tenders are returned. |

The two-tier split exists specifically because `CLOSED` records carry materially less new information than the other three event types: `NEW_LISTING`/`STATUS_CHANGE`/`UPDATED` all deliver a freshly walked, fully detailed tender row, while `CLOSED` is inferred purely from a tender's absence and produces no new content beyond the fact of closure — hence `result` vs. the cheaper `result-summary`.

Because `onlyNew` (delta mode) is a filter on what gets *created* as a dataset record, not a discount on records that are created: an unchanged tender (`estado` and content identical to the last run) is simply never emitted as a `NEW_LISTING`/`STATUS_CHANGE`/`UPDATED`/`CLOSED` record when delta mode is on, so it is never billed at all — not billed at $0, it is not turned into a chargeable event in the first place. Only genuine changes generate cost. On a full non-delta run, every fetched tender is returned as `UNCHANGED` or another applicable event type and billed at the `result` rate.

A daily recurring monitor that finds around 5 changes a day across the active register costs roughly $0.02/day (about $0.45/month), plus the negligible per-run `apify-actor-start` charge.

***

*Sources: `.actor/actor.json`, `.actor/input_schema.json`, `.actor/dataset_schema.json`, `.actor/output_schema.json`, `README.md`, `CHANGELOG.md`, `AGENTS.md` — all read directly from the actor's repository at `C:\Users\Stef\apify-portfolio\cordoba-compras-monitor`.*

# Actor input Schema

## `maxItems` (type: `integer`):

Hard cap on the number of active tenders (licitaciones) returned this run. The source only covers active/open tenders (Licitaciones) - awarded or historical tenders are out of scope for this actor.

## `proxyConfiguration` (type: `object`):

Required: webecommerce.cba.gov.ar blocks non-residential-Argentina traffic from the cloud (verified live - ConnectTimeoutError from Apify's datacenter IPs). Defaults to Residential + Argentina; do not switch to datacenter.

## `onlyNew` (type: `boolean`):

When enabled, persists which tender ids (nroCotizacion) this actor has already returned - and their last-known estado + content fingerprint - in its own named key-value store that survives between scheduled runs, and returns only tenders that are new, changed estado, amended, or closed since a prior run. IMPORTANT: this source's listing is NOT reliably sorted newest-first end to end (verified live - a real tender can appear out of order relative to both its own Fecha Inicio and its own nroCotizacion, within the very first page), so onlyNew filters AFTER a full fetch (up to maxItems) rather than stopping pagination early like this fleet's other delta-enabled actors. It is correct, just not a pagination-cost optimization. Independent of dateRange. Recommended for recurring monitoring; leave off for a full one-off extraction.

## `eventTypes` (type: `array`):

Which kinds of change to deliver when 'Delta mode' is on (ignored, everything delivered, when it is off). NEW\_LISTING = never seen before. STATUS\_CHANGE = seen before, estado changed - free to detect. UPDATED = seen before, same estado, a field changed (a prorroga granted, an amended item/budget). CLOSED = a previously-active tender is absent from this run's fetch - only computed when maxItems does not truncate the walk (a partial walk cannot prove absence).

## `dateRange` (type: `string`):

Optionally restrict results to tenders whose Fecha Inicio (publication date) falls within this window. Independent of onlyNew - this is the source's own declared publication timestamp, not independently cross-verified against when a tender actually first appeared on the portal.

## Actor input object example

```json
{
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AR"
  },
  "onlyNew": false,
  "eventTypes": [
    "NEW_LISTING",
    "STATUS_CHANGE",
    "UPDATED",
    "CLOSED"
  ]
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "AR"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("stefano_seggio/cordoba-compras-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "AR",
    } }

# Run the Actor and wait for it to finish
run = client.actor("stefano_seggio/cordoba-compras-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AR"
  }
}' |
apify call stefano_seggio/cordoba-compras-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stefano_seggio/cordoba-compras-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/q9jhMgJRSGjyNbXKA/builds/OMi8YkKneBrRaPOex/openapi.json
