# Spain Grants BDNS - subvenciones, ayudas y convocatorias (`opendata_es/spain-grants-bdns`) Actor

Every grant call in Spain's BDNS/SNPSAP: filter by sector, authority, region, amount and open deadline; daily novelty diff and budget alerts. Subvenciones, ayudas y convocatorias publicas de Espana como datos (BDNS, SNPSAP, BOE). No personal data.

- **URL**: https://apify.com/opendata_es/spain-grants-bdns.md
- **Developed by:** [c.d.s Studio](https://apify.com/opendata_es) (community)
- **Categories:** Lead generation, Open source
- **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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## 💶 Spain Grants BDNS — subvenciones de España (Base de Datos Nacional de Subvenciones)

> Every Spanish grant call (**convocatoria**) from the official BDNS/SNPSAP,
> **filtered, ranked, diffed and with budget alerts**.
> Todas las convocatorias de subvenciones publicadas en España, filtrables por
> sector, órgano, comunidad, importe y plazo — **con novedades diarias y alertas
> por umbral de presupuesto**. Sin datos personales de beneficiarios.

***

### 🇪🇸 Español

#### Qué hace

El IGAE publica en la **BDNS** (infosubvenciones.es) las +658.000 convocatorias
de subvenciones de todas las administraciones españolas. La API pública es
gratis pero **incómoda de usar en producción**:

| Problema de la fuente oficial | Qué hace este actor |
|---|---|
| +658.000 registros sin más filtro que texto y fecha | Filtras por **finalidad/sector**, **órgano convocante**, **ámbito** (Estado/CCAA/local), **comunidad autónoma**, **importe** y **plazo abierto** |
| La búsqueda solo devuelve 8 campos: **ni presupuesto ni plazos** | Enriquece cada convocatoria con su detalle: importe, fechas de solicitud, sectores CNAE, reglamento, bases reguladoras |
| **Página de 300 como máximo** y sin cursor | Paginación automática con tope configurable (`maxPages`) |
| Formatos de fecha caprichosos (registro `dd/mm/yyyy`, vigencia `yyyymmdd`) | Tú escribes ISO (`2026-10-16`); el actor traduce |
| Nombres de región con código pegado (`ES12 - PRINCIPADO DE ASTURIAS`, grupos tipo `ES1 - NOROESTE`) | Ecribes «Asturias», «Canarias», «ES51» y resuelve los IDs internos |
| Sin ranking: imposible ver «las gordas» | **Top-N por importe** por ámbito, mayor presupuesto primero |
| Los avisos solo existen como email de la propia web | **Novedades contra el run anterior** (`newSinceLastRun`) + **alertas por umbral de presupuesto** con webhook |
| La BDNS también expone concesiones con **nombres y NIF de beneficiarios** | Este actor **solo toca convocatorias**: cero datos personales en la salida |

#### Para quién

- **Asesorías y gestorías** — vigilar convocatorias de su comunidad para pymes/autónomos.
- **Consultores de financiación / fondos UE** — detectar líneas nuevas por importe y sector.
- **Startups y SaaS de subvenciones** — el feed listo para pintar, sin pelearse con la API.
- **Gabinetes de prensa y análisis** — qué dinero se convocó esta semana y quién lo convocó.

#### Ejemplos de entrada

Convocatorias de los últimos 7 días en Canarias, con plazo abierto ahora,
presupuesto ≥ 50.000 €, y avísame por webhook de las de ≥ 500.000 €:

```json
{
  "regions": ["Canarias"],
  "daysBack": 7,
  "openNow": true,
  "minImporte": 50000,
  "alertThreshold": 500000,
  "alertWebhookUrl": "https://hooks.tu-dominio.com/subvenciones"
}
```

Solo cultura, estatales, las 5 mayores:

```json
{ "finalidades": ["Cultura"], "ambito": "ESTADO", "topN": 5 }
```

Vigilancia diaria de un órgano concreto:

```json
{ "organs": ["Cabildo de Tenerife"], "daysBack": 1, "storeHistory": true }
```

Los nombres admiten tildes o no, mayúsculas o no, y código NUTS (`ES70`) o
nombre («Canarias»). Las finalidades son las del catálogo oficial: «Cultura»,
«Empleo», «Investigación», «Comercio, Turismo y Pymes»...

#### Salida (una fila por convocatoria, mayor presupuesto primero)

```json
{
  "codigoBDNS": "935634",
  "descripcion": "Resolución de 14-10-2026 por la que se autoriza el gasto ... gasificación de nuevos núcleos de población",
  "fechaRecepcion": "2026-10-15",
  "ambito": "AUTONOMICA",
  "administracion": "PRINCIPADO DE ASTURIAS",
  "organo": "CONSEJERÍA DE CIENCIA, EMPRESAS, FORMACIÓN Y EMPLEO",
  "presupuestoTotalEur": 150000,
  "abierto": true,
  "fechaInicioSolicitud": "2026-01-01",
  "fechaFinSolicitud": "2026-12-31",
  "finalidad": "Industria y Energía",
  "sectores": [
    { "codigo": "35.4", "nombre": "Actividades de intermediación de energía eléctrica y gas natural" }
  ],
  "regiones": ["ES120 - Asturias"],
  "tiposBeneficiario": ["GRAN EMPRESA"],
  "instrumentos": ["SUBVENCIÓN Y ENTREGA DINERARIA SIN CONTRAPRESTACIÓN"],
  "reglamento": "REG (UE) 2023/2832 de minimis, SIEG",
  "basesReguladoras": "Presupuestos generales Principado de Asturias 2026",
  "urlBasesReguladoras": "https://miprincipado.asturias.es/bopa/2025/12/31/20251231Su1.pdf",
  "aboveThreshold": true,
  "newSinceLastRun": true,
  "rank": 1,
  "rankScope": "AUTONOMICA",
  "url": "https://www.infosubvenciones.es/bdnstrans/GE/es/convocatorias/935634",
  "scrapedAt": "2026-10-16T03:10:00+00:00"
}
```

Además:

- **`OUTPUT`** (key-value store): resumen del run — convocatorias tras filtros,
  detalles conseguidos, presupuesto mínimo/máximo/medio/total, nº de alertas,
  nº de plazos abiertos y novedades desde el run anterior.
- **Dataset `subvenciones-historico`**: una fila por convocatoria/día.
  Ejecútalo con un **Schedule** diario y tienes la serie de publicación que la
  web oficial no conserva en formato consultable.

#### Programarlo

Apify Console → *Schedules* → cron `0 7 * * *` con `daysBack: 1`: cada mañana
las convocatorias del día anterior que cumplen tu filtro, con las nuevas
marcadas y las gordas disparando el webhook.

#### Honestidad sobre los datos

- **Detalles con tope**: el presupuesto, los plazos y los sectores no vienen en
  la búsqueda; se piden convocatoria a convocatoria (bridado: `maxDetails`, por
  defecto 500). Las filas sin detalle se entregan igualmente, sin importe.
- El filtro por importe solo se aplica sobre filas con detalle conseguido.
- La BDNS avisa en cada respuesta de que puede **restringir el API ante
  abuso**: el actor pagina con pausa y enriquece con concurrencia limitada.
- Los datos de la BDNS son dinámicos (correcciones a posteriori). La fuente es
  el IGAE; el aviso legal de reutilización está en
  [infosubvenciones.es/bdnstrans/GE/es/avisolegal](https://www.infosubvenciones.es/bdnstrans/GE/es/avisolegal).
- **Sin datos personales**: el actor no consulta concesiones ni terceros; la
  salida solo contiene lo publicado en la convocatoria (quién convoca, cuánto,
  para quién en términos de tipo de beneficiario).

***

### 🇬🇧 English

#### What it does

Spain's IGAE publishes every grant call (**convocatoria**) from every Spanish
administration in the **BDNS/SNPSAP** (+658k records). The public API is free
but rough: a text-only search capped at 300 rows per page, dates in two
different quirky formats, region names glued to NUTS codes, no budget or
deadline in the search results, and no ranking, diffing or alerting.

This actor gives you that data **production-ready**:

- Filters: **sector (finalidad)**, **granting authority**, level
  (State/regional/local), **autonomous community** (name or NUTS code),
  **budget range**, **application window open** on a date.
- Enriched items: budget, deadlines, CNAE sectors, regulation, bases, EU funds.
- **Top-N by budget**, biggest first.
- **Daily novelty diff** (`newSinceLastRun`) and **budget-threshold alerts**
  with optional webhook POST.
- **No beneficiary personal data** — grant calls only, never concesiones.

#### Who buys this

Grant advisory firms tracking calls for their clients, EU-funding consultants,
subsidy SaaS products that need the feed, and newsrooms analyzing where public
money is being offered.

***

### 💰 Pricing — pay per event

Este actor se monetiza por **evento**. El reparto de Apify es **80 % para el
desarrollador**. Mismos nombres de evento que el actor de carburantes.

| Evento | Cuándo se cobra | Precio |
|---|---|---|
| `actor-start` | 1 por ejecución (cubre búsqueda paginada + catálogos + filtrado) | **$0,02** |
| `station-result` | 1 por convocatoria entregada, limpia, rankeada y enriquecida | **$0,0004** |
| `history-snapshot` | 1 por ejecución que añade el día al histórico y marca novedades | **$0,01** |
| `price-alert` | 1 por convocatoria igual o por encima del umbral pedido | **$0,001** |

Los valores exactos están en [`.actor/pay_per_event.json`](.actor/pay_per_event.json)
(mismos nombres de evento que cobra `src/main.py`).

#### En qué horquilla de ingresos

Con los números medidos hoy (~300 convocatorias/día publicadas, 1 página de
búsqueda por día). Tipo de cambio aproximado 0,92 €/$. **Estimaciones, no
ingresos garantizados**.

| Perfil de cliente | Consumo mensual | Bruto/mes | Neto dev (80 %) |
|---|---|---|---|
| **A — gestoría local**: 1 comunidad, 1 ejecución/día | 30 runs × (0,02 + 300×0,0004 + 0,01) | $6,90 | **$5,5 ≈ 5 €** |
| **B — asesoría multirregión**: 3 comunidades, 3 filtros/día | 90 runs × ≈0,16 | $14,40 | **$11,5 ≈ 10,6 €** |
| **C — SaaS de subvenciones**: nacional semanal + 17 CCAA diarias | ≈120 runs × ≈0,16 + 4 runs grandes | ≈$22 | **$17,6 ≈ 16 €** |
| **D — combinación** (1 C + 2 B + 3 A) | suma | ≈$52 | **$42 ≈ 38 €** |

Un actor de nicho: se apoya en el mismo pricing y la misma cuenta que el de
carburantes; cada cliente extra suma sin coste marginal.

***

### 🛠️ Desarrollo y publicación

```bash
## 1. Tests del núcleo contra la API real de la BDNS (17 tests)
python -m pytest tests/test_subvenciones.py -v

## 2. Ejecución local completa (crea ./storage con dataset y OUTPUT)
python -m venv .venv && .venv/Scripts/pip install -r requirements.txt
##    deja tu entrada en storage/key_value_stores/default/INPUT.json
.venv/Scripts/python -m src

## 3. Publicación en Apify Store
npx -y apify-cli login            # abre el navegador, pega el API token
npx -y apify-cli push             # construye la imagen y sube el actor
##    luego, en Apify Console:
##    Monetization -> Pay per event -> pegar los 4 eventos de .actor/pay_per_event.json
##    Publication  -> Publish to Store (categoría: Business)
```

#### Estructura

```
.actor/
  actor.json            # metadatos del actor
  input_schema.json     # formulario de entrada (20 campos)
  dataset_schema.json   # vista de tabla «Biggest budget first» en la Console
  pay_per_event.json    # pricing exacto a pegar en Monetization
src/
  subvenciones.py       # núcleo puro: busca, normaliza, filtra, enriquece, diffea
  main.py               # wrapper de plataforma: storages, cobro, webhook, OUTPUT
  __main__.py
tests/
  test_subvenciones.py  # 17 tests; 6 de ellos contra la API real
Dockerfile              # apify/actor-python:3.12
```

Fuente: `https://www.infosubvenciones.es/bdnstrans/api/`
(API pública del Sistema Nacional de Publicidad de Subvenciones y Ayudas
Públicas, IGAE — Ministerio de Hacienda).

# Actor input Schema

## `searchText` (type: `string`):

Words that must all appear in the grant title (e.g. 'digitalizacion pymes'). Accents don't matter.

## `daysBack` (type: `integer`):

Registration-date window ending today. 0 = no date filter (whole database, subject to maxPages).

## `dateFrom` (type: `string`):

Explicit registration-date range start (yyyy-mm-dd). Overrides daysBack.

## `dateTo` (type: `string`):

Explicit registration-date range end (yyyy-mm-dd).

## `openNow` (type: `boolean`):

Only grants whose application window is open today (server-side vigencia filter).

## `openOn` (type: `string`):

Only grants whose application window is open on this date (yyyy-mm-dd). Overrides openNow.

## `regions` (type: `array`):

Autonomous communities or provinces by name or NUTS code: 'Canarias', 'Asturias', 'ES51'. Empty = all Spain.

## `finalidades` (type: `array`):

BDNA purpose categories: 'Cultura', 'Empleo', 'Investigacion', 'Comercio, Turismo y Pymes'...

## `organs` (type: `array`):

Text match on the authority: 'Ministerio de Cultura', 'Cabildo de Tenerife', 'Ayuntamiento de Madrid'.

## `ambito` (type: `string`):

State, regional or local government level.

## `minImporte` (type: `number`):

Grants with total budget at or above this amount (EUR). Needs details, subject to maxDetails.

## `maxImporte` (type: `number`):

Grants with total budget at or below this amount (EUR).

## `alertThreshold` (type: `number`):

Grants with budget at or above this amount are flagged aboveThreshold and trigger the alert webhook.

## `onlyOpen` (type: `boolean`):

Extra client-side check on the detail's abierto flag. Requires fetchDetails.

## `topN` (type: `integer`):

0 = every match. Otherwise keep the N biggest budgets per administration level.

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

Hard cap on dataset rows. 0 = no cap.

## `maxPages` (type: `integer`):

Hard cap on BDNS search pages (300 grants each). The source has 650k+ rows; keep it bounded.

## `fetchDetails` (type: `boolean`):

Fetches each grant's detail (budget, deadlines, sectors, regions). Throttled.

## `maxDetails` (type: `integer`):

Cap on detail requests per run (politeness to the public BDNS API).

## `storeHistory` (type: `boolean`):

Appends one row per grant/day to the named dataset and flags new ones since the previous run.

## `alertWebhookUrl` (type: `string`):

Optional. Receives a POST with the grants at or above alertThreshold.

## Actor input object example

```json
{
  "daysBack": 7,
  "openNow": false,
  "regions": [
    "CANARIAS"
  ],
  "ambito": "",
  "onlyOpen": false,
  "topN": 0,
  "maxItems": 0,
  "maxPages": 4,
  "fetchDetails": true,
  "maxDetails": 500,
  "storeHistory": true
}
```

# Actor output Schema

## `grants` (type: `string`):

Every grant returned by this run as dataset items: BDNS code, title, authority, region, budget in EUR, application deadlines, open flag, sectors, rank, novelty flag and info page link.

## `summary` (type: `string`):

One JSON record with the counts of the run: grants scanned, grants returned, details fetched, min/max/avg budget, alerts fired and new grants since the previous run.

# 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 = {
    "regions": [
        "CANARIAS"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("opendata_es/spain-grants-bdns").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 = { "regions": ["CANARIAS"] }

# Run the Actor and wait for it to finish
run = client.actor("opendata_es/spain-grants-bdns").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 '{
  "regions": [
    "CANARIAS"
  ]
}' |
apify call opendata_es/spain-grants-bdns --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,opendata_es/spain-grants-bdns"
        }
    }
}
```

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/edMoT5B06fwJGVIxf/builds/mWF8WmEXzXD4vcdr9/openapi.json
