# Catalonia Public Tenders Scraper (PSCP) (`xamto1987/catalonia-tenders`) Actor

Licitaciones publicas de Cataluna desde el dataset Socrata de la Plataforma de serveis de contractacio publica (PSCP), con enriquecimiento opcional del expediente completo. Catalonia public procurement tenders and awards.

- **URL**: https://apify.com/xamto1987/catalonia-tenders.md
- **Developed by:** [Santi Belloso Lopez](https://apify.com/xamto1987) (community)
- **Categories:** Lead generation, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

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

## Catalonia Public Tenders Scraper (PSCP) · Licitaciones públicas de Cataluña

Extrae **licitaciones y adjudicaciones públicas de Cataluña** desde el dataset
oficial de open data (Socrata) de la **Plataforma de serveis de contractació
pública (PSCP)** de la Generalitat de Catalunya, con enriquecimiento opcional
del **expediente completo** (órgano de contratación, lotes, adjudicatarios
por lote).

Scrapes **Catalonia public procurement tenders and awards** from the
Generalitat's official Socrata open-data catalogue, with optional full
tender-record enrichment.

***

### 🇪🇸 Español

#### Qué obtienes

Un ítem por publicación (anuncio, adjudicación, formalización…) del dataset
maestro de la PSCP:

| Campo | Qué es |
|---|---|
| `idExpediente`, `idIntern` | Número de expediente del órgano y ID interno de la PSCP |
| `titulo`, `objeto` | Título de la publicación y objeto del contrato |
| `administracion`, `organoContratacion`, `organoSuperior` | Quién contrata, por niveles |
| `tipoContrato`, `procedimiento`, `tramitacion` | Servicios/Obras/Suministros…, procedimiento de adjudicación |
| `estado` | Fase real del dataset (`Anunci de licitació`, `Adjudicació`, `Formalització`, `Execució`…) |
| `presupuestoLicitacionSinIva`/`ConIva`, `valorEstimadoContrato` | Importes **como número** |
| `importeAdjudicacionSinIva`/`ConIva` | Importe de adjudicación, como número |
| `adjudicatario.nif`, `.razonSocial` | Empresa adjudicataria — **dato mercantil, se publica** |
| `codigosCpv`, `lugarEjecucion`, `codigoNuts` | Clasificación económica y territorio |
| `fechaPublicacionAnuncio`, `fechaLimiteOfertas`, `fechaPublicacionAdjudicacion`, `fechaPublicacionFormalizacion` | Fechas clave |
| `enlaceFichaPublica` | Ficha pública en `contractaciopublica.cat` |
| `enlacesJson` | Todas las URLs de detalle disponibles en la fila (`licitacio`, `adjudicacio`, `formalitzacio`…) |

Con `conDetalle: true` se añade además `detalle` (organo de contratación,
lotes y adjudicatarios por lote — ver más abajo).

#### Parámetros de entrada

| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `keywords` | lista de textos | `[]` | Busca en el **objeto del contrato** y en el título, en servidor. Vacío = sin filtro |
| `tipoContrato` | enum | — | Valor real del dataset (`Serveis`, `Obres`, `Subministraments`…) |
| `estado` | `anuncio`|`adjudicacion`|`formalizacion` | — | Fase de publicación. Vacío = todas |
| `importeMinimo` / `importeMaximo` | número | — | Filtro en servidor por presupuesto/importe de adjudicación (EUR) |
| `fechaDesde` | fecha `AAAA-MM-DD` | — | Desde esta fecha de publicación de la fase elegida. Recomendado para ejecuciones diarias (el dataset se actualiza **a diario**) |
| `conDetalle` | booleano | `false` | Ver más abajo. ⚠️ Multiplica las peticiones HTTP |
| `incluirContactosNominales` | booleano | `false` | Ver más abajo |
| `maxItems` | entero | `500` | Tope de ítems; el actor para limpiamente al alcanzarlo |

Ejemplo:

```json
{
  "keywords": ["neteja"],
  "tipoContrato": "Serveis",
  "estado": "adjudicacion",
  "maxItems": 200
}
```

#### El filtrado ocurre en el servidor, no en el cliente

Este actor traduce el input a **SoQL** (`$where`, `$select`, `$order`,
`$limit`/`$offset`) y lo envía al servidor Socrata: nunca descarga de más
para filtrar después en memoria. El propio log de la ejecución muestra la
cláusula `$where` exacta que se ha enviado.

#### `conDetalle`: el expediente completo

Cada fila del dataset trae ya varias URLs `url_json_*` que apuntan al JSON
completo del expediente en `contractaciopublica.cat/portal-api/...` —
**sin necesidad de autenticación**: el token va firmado en la propia URL,
copiado del open data. Con `conDetalle: true` el actor sigue la URL de la
fase más avanzada disponible (formalización > adjudicación > licitación >
publicación agregada…) y añade al ítem:

- `detalle.organo` — nombre, NIF, dirección, teléfono y email
  **institucionales** del órgano de contratación (esto se conserva siempre).
- `detalle.lotes[]` — por cada lote: importe de adjudicación, CPV principal
  y adjudicatarios (razón social, NIF, tipo de empresa, país, provincia).
- `detalle.normativaAplicable`, `.tipoTramitacion`, `.procedimientoAdjudicacion`,
  `.presupuestoLicitacionConIva`, `.financiadoFondosUe`.

⚠️ **Esto es una petición HTTP adicional por cada licitación guardada** —
duplica (o más) el número de peticiones totales y, como el actor respeta
\~1 req/s, el tiempo de ejecución crece en la misma proporción. Actívalo solo
si necesitas ese nivel de detalle.

#### Protección de datos: nombres de personas físicas fuera por defecto

`incluirContactosNominales` protege **dos cosas distintas bajo el mismo
flag**, porque son la misma categoría de dato (identificación nominal de una
persona física concreta):

1. **Siempre, en el listado base** (no requiere `conDetalle`): una parte de
   los adjudicatarios de contratos públicos catalanes son **autónomos**
   (persona física), no empresas. El NIF ya llega **enmascarado por la
   propia fuente** (`"*** 1014 **"`), pero el **nombre y apellidos llegan
   sin enmascarar** (`"Carolina Grecia Ferrer Mateu"`). Verificado con una
   muestra real de 25 adjudicaciones el 23-ago-2026: **2 de 25** eran
   persona física. Con `incluirContactosNominales: false` — el valor por
   defecto — `adjudicatario.nif` y `.razonSocial` se anulan y solo queda
   `adjudicatario.tipoPersona` (`fisica`/`juridica`/`desconocido`, con la
   misma clasificación de NIF que usa `spain-grants-bdns`, fail-closed). El
   resto del contrato (importe, órgano, fechas…) se conserva: es gasto
   público, no dato personal.
2. **Solo si `conDetalle: true`**: el JSON de expediente completo incluye,
   para cada órgano de contratación, una lista `personesContacte` con
   **nombre, apellidos, cargo, teléfono y email de personas físicas
   concretas** (funcionarios/as del órgano: la secretaria, la interventora…).
   Con el flag desactivado esos campos **no aparecen en el dataset, ni
   siquiera vacíos**. Solo se conserva la información del **órgano en sí**
   (nombre de la institución, NIF, dirección, teléfono/email
   institucionales), que no es un dato personal.

Si activas `incluirContactosNominales`, el dataset incluirá nombres de
autónomos adjudicatarios y, con `conDetalle`, también nombre, cargo,
teléfono y email de empleados públicos concretos: su tratamiento y
redifusión posterior son responsabilidad tuya conforme al **RGPD** y la
**LOPDGDD**, aunque su publicación original esté amparada por la normativa
de contratación pública.

#### Cuánto tarda

El actor respeta un máximo de **~1 petición por segundo**, tanto para el
listado como para el detalle (comparten la misma cola).

- Sin `conDetalle`: 100 ítems por petición de listado → rápido.
- Con `conDetalle`: una petición de detalle adicional por ítem guardado →
  \~1 s por ítem, más las peticiones de listado.

#### Fuente y aviso legal

- **Fuente oficial**: dataset Socrata `ybgg-dgi6` ("Contractació pública a
  Catalunya: publicacions a la Plataforma de serveis de contractació
  pública"), catálogo de dades obertes de la Generalitat de Catalunya —
  `https://analisi.transparenciacatalunya.cat/resource/ybgg-dgi6.json`.
  1.961.041 filas y 67 campos verificados el 23-ago-2026; actualización
  diaria confirmada por metadato oficial y cabecera `X-SODA2-Truth-Last-Modified`.
- El JSON de detalle procede de `contractaciopublica.cat/portal-api/`, el
  propio portal PSCP; su `robots.txt` permite el rastreo (`Allow: /`) pero
  declara `Crawl-delay: 1`, `Request-rate: 60/1m` y una ventana de rastreo
  preferente `Visit-time: 18:00-07:00`. El actor siempre respeta ~1 req/s;
  para volcados masivos con `conDetalle: true`, programa la ejecución en esa
  franja nocturna si quieres respetar también la ventana horaria.
- **Licencia**: reutilización libre según la **Llei 19/2014, de 29 de
  desembre, de transparència, accés a la informació pública i bon govern**
  (art. 17.1: *"la reutilització de la informació pública és lliure i no
  està subjecta a restriccions"*), con obligación de citar la fuente.
  Es el marco legal más favorable de todas las fuentes evaluadas en este
  proyecto.
- Este actor **solo lee datos públicos**, sin login, sin cookies y sin
  saltarse ninguna medida técnica. No es un producto oficial ni está
  afiliado, respaldado ni verificado por la Generalitat de Catalunya.
- Los datos son dinámicos y pueden corregirse tras la extracción: para
  decisiones con efectos jurídicos, consulta siempre `enlaceFichaPublica`.

***

### 🇬🇧 English

#### What it does

`catalonia-tenders` scrapes **Catalonia's public procurement tenders and
awards** from the Generalitat's official Socrata open-data dataset (PSCP —
*Plataforma de serveis de contractació pública*), with optional enrichment
from the full tender record served by the portal itself.

#### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `keywords` | string list | `[]` | Server-side search over the contract object and title |
| `tipoContrato` | enum | — | Real dataset value (Catalan): `Serveis`, `Obres`, `Subministraments`… |
| `estado` | enum | — | `anuncio` / `adjudicacion` / `formalizacion` (mapped to the real dataset phase) |
| `importeMinimo` / `importeMaximo` | number | — | Server-side amount filter (EUR) |
| `fechaDesde` | ISO date | — | From this publication date on; recommended for daily incremental runs |
| `conDetalle` | boolean | `false` | Chains a request to the full tender JSON. ⚠️ roughly doubles HTTP requests |
| `incluirContactosNominales` | boolean | `false` | See below |
| `maxItems` | integer | `500` | Hard cap; the actor stops cleanly |

#### Server-side filtering

Every input is translated to **SoQL** (`$where`, `$select`, `$order`,
`$limit`/`$offset`) and sent to the Socrata server — nothing is downloaded
in bulk to be filtered client-side. The run log prints the exact `$where`
clause sent.

#### GDPR: named individuals excluded by default

`incluirContactosNominales` guards two things: (1) always, in the base
listing — some Catalan tender awardees are **sole traders** (natural
persons), and while their tax ID comes source-masked, their **full name
does not** (verified: 2 of 25 real awards in a sample were natural persons)
— disabled by default, `adjudicatario.nif`/`.razonSocial` are nulled,
keeping only `.tipoPersona`; (2) only with `conDetalle` — the full tender
record lists named contact people (name, surname, role, phone, email) for
the contracting body's staff, entirely absent from the output by default.
Only the contracting body's own institutional data is kept in both cases.
Enabling the flag adds named individuals' personal data to the dataset; you
become responsible for its processing under **GDPR**.

#### Disclaimer

Data comes from the Generalitat de Catalunya's official Socrata open-data
dataset (`ybgg-dgi6`) and, when `conDetalle` is enabled, from the PSCP
portal's own `portal-api`. This actor is **not** an official product and is
not affiliated with or endorsed by the Generalitat de Catalunya. Reuse is
free under **Llei 19/2014, art. 17.1** (attribution required). Data is
dynamic — for legally binding decisions, always check the official record
linked in `enlaceFichaPublica`.

***

**Palabras clave / Keywords**: licitaciones Cataluña, contratación pública
Cataluña, PSCP, contractació pública, Generalitat de Catalunya, tenders
Catalonia, public procurement Spain, adjudicaciones públicas, Socrata open
data, CPV, NUTS, open data Cataluña.

# Actor input Schema

## `keywords` (type: `array`):

Lista de terminos a buscar en el objeto del contrato (`objecte_contracte`) y en el titulo de la publicacion (`denominacio`). Se lanza UNA consulta al servidor con todos los terminos combinados por OR; cualquier termino coincide. Vacio = sin filtro de texto (se pagina el dataset completo, ordenado por fecha). / Server-side search across the contract object and the publication title; empty = no text filter.

## `tipoContrato` (type: `string`):

Filtra por `tipus_contracte`, tal cual lo publica el dataset (valores reales verificados con `$group`, en catalan). Vacio = todos los tipos. / Filters by the dataset's real `tipus_contracte` value (Catalan). Empty = all types.

## `estado` (type: `string`):

Fase de publicacion del expediente (`fase_publicacio` del dataset). `anuncio` = en plazo de presentacion de ofertas; `adjudicacion` = ya adjudicado; `formalizacion` = contrato formalizado. Vacio = todas las fases (incluye tambien ejecucion, anulaciones y publicaciones agregadas trimestrales). / Real dataset phase, exposed with Spanish aliases; empty = all phases.

## `importeMinimo` (type: `integer`):

Filtra en servidor por el presupuesto de licitacion sin IVA (o, si no existe, el importe de adjudicacion sin IVA). / Server-side filter on tender budget (or, failing that, award amount), both VAT-excl.

## `importeMaximo` (type: `integer`):

Igual que `importeMinimo` pero como cota superior. / Same field, upper bound.

## `fechaDesde` (type: `string`):

Solo expedientes publicados a partir de esta fecha. Se aplica sobre la fecha de publicacion de la fase elegida en `estado` (anuncio/adjudicacion/formalizacion); sin `estado`, se aplica sobre la fecha de anuncio. Formato ISO `AAAA-MM-DD`. Recomendado para ejecuciones incrementales diarias (el dataset se actualiza a diario). / ISO date; applies to the publication date of the chosen `estado` phase (announcement date by default). Recommended for daily incremental runs.

## `conDetalle` (type: `boolean`):

DESACTIVADO POR DEFECTO. Al activarlo, por cada licitacion guardada el actor hace UNA PETICION ADICIONAL al JSON completo del expediente en el portal (`contractaciopublica.cat/portal-api/...`), con datos del organo de contratacion, lotes y adjudicatarios por lote. ⚠️ Esto MULTIPLICA el numero de peticiones HTTP (aprox. x2) y, por tanto, el tiempo de ejecucion, ya que el actor respeta ~1 peticion/segundo. / OFF by default. Chains one extra request per saved item to the full tender record — roughly doubles HTTP requests and run time given the 1 req/s rate limit.

## `incluirContactosNominales` (type: `boolean`):

DESACTIVADO POR DEFECTO. Protege DOS cosas distintas bajo el mismo flag, porque son la misma categoria de dato (identificacion nominal de una persona fisica concreta): (1) SIEMPRE, en el listado base: si el adjudicatario de un contrato es un AUTONOMO (persona fisica) en vez de una empresa, su NIF ya llega enmascarado por la propia fuente pero su NOMBRE COMPLETO no — con esta opcion desactivada, `adjudicatario.nif` y `.razonSocial` se anulan y solo queda `adjudicatario.tipoPersona`. (2) Solo si `conDetalle` esta activado: el JSON de detalle incluye, para cada organo de contratacion, una lista de personas de contacto CON NOMBRE, APELLIDOS, CARGO, TELEFONO Y EMAIL (personal publico) — con esta opcion desactivada, esos campos no aparecen en el dataset. Activarla incluye datos personales: su tratamiento y redifusion quedan bajo tu responsabilidad (RGPD / LOPDGDD). / OFF by default. Guards two things under one flag — same data category (a named natural person's identity): (1) always, in the base listing: sole-trader (persona fisica) awardees have their name published unmasked even though their tax ID is source-masked — disabling this nulls out `adjudicatario.nif`/`.razonSocial`; (2) only with `conDetalle`: strips named contact-person fields from the contracting body's detail.

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

Numero maximo de licitaciones a guardar en el dataset. El actor para limpiamente al alcanzarlo. / Hard cap on dataset items.

## Actor input object example

```json
{
  "keywords": [
    "neteja",
    "obres"
  ],
  "tipoContrato": "",
  "estado": "",
  "conDetalle": false,
  "incluirContactosNominales": false,
  "maxItems": 500
}
```

# Actor output Schema

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

Licitaciones publicas de Cataluna (PSCP) normalizadas. JSON, CSV o Excel.

# 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 = {
    "keywords": [
        "neteja",
        "obres"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("xamto1987/catalonia-tenders").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 = { "keywords": [
        "neteja",
        "obres",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("xamto1987/catalonia-tenders").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 '{
  "keywords": [
    "neteja",
    "obres"
  ]
}' |
apify call xamto1987/catalonia-tenders --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,xamto1987/catalonia-tenders"
        }
    }
}

```

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/bS27Iudx9bx3qEqqM/builds/hvBXXm7TUpCSt1Zug/openapi.json
