El BDNS (Base de Datos Nacional de Subvenciones) es el repositorio oficial del Ministerio de Hacienda con todas las convocatorias de subvenciones públicas de España — estado, comunidades autónomas, diputaciones y ayuntamientos.
El problema: la interfaz web del BDNS es lenta, poco filtrable y no tiene ninguna opción de notificación automática. Si quieres monitorizar subvenciones para tu empresa o tus clientes, necesitas estar entrando manualmente todos los días.
En este tutorial te mostramos cómo automatizarlo completamente.
Qué datos expone el BDNS
Cada convocatoria incluye:
- Título y descripción de la convocatoria
- Órgano convocante (ministerio, consejería, ayuntamiento…)
- Importe total de la convocatoria
- Beneficiario (empresas, autónomos, investigadores, ONGs…)
- CCAA a la que aplica
- Sector económico
- Fecha de convocatoria y fecha de resolución
- Estado: abierta, resuelta, archivada
- URL oficial en el BDNS para más detalles
Obtener tu API key
Primero necesitas una API key gratuita — 20 requests/mes sin tarjeta:
- Ve a apispain.es → Empezar gratis
- Introduce tu email y guarda la key
Consultar subvenciones abiertas
Con curl
# Todas las subvenciones abiertas
curl "https://api.apispain.es/v1/bdns/subvenciones?abiertas=true" \
-H "Authorization: Bearer TU_API_KEY"
# Solo en Madrid
curl "https://api.apispain.es/v1/bdns/subvenciones?ccaa=Madrid&abiertas=true" \
-H "Authorization: Bearer TU_API_KEY"
# Por sector
curl "https://api.apispain.es/v1/bdns/subvenciones?sector=tecnologia&abiertas=true" \
-H "Authorization: Bearer TU_API_KEY"
Respuesta:
[
{
"id": "abc123",
"bdnsId": "BDNS-789456",
"convocatoria": "Ayudas para la modernización del sector industrial",
"organoConvocante": "Ministerio de Industria y Turismo",
"importe": null,
"importeTotal": "15000000.00",
"beneficiario": "PYMEs industriales",
"ccaa": "Nacional",
"sector": "industria",
"fechaConvocatoria": "2026-03-01T00:00:00.000Z",
"fechaResolucion": "2026-06-30T00:00:00.000Z",
"estado": "abierta",
"urlBdns": "https://www.infosubvenciones.es/bdnstrans/GE/es/convocatoria/789456"
}
]
Con JavaScript
const API_KEY = process.env.APISPAIN_API_KEY
async function buscarSubvenciones({ ccaa, sector, importeMin } = {}) {
const url = new URL('https://api.apispain.es/v1/bdns/subvenciones')
url.searchParams.set('abiertas', 'true')
if (ccaa) url.searchParams.set('ccaa', ccaa)
if (sector) url.searchParams.set('sector', sector)
if (importeMin) url.searchParams.set('importeMin', importeMin)
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` }
})
if (!res.ok) throw new Error(`Error ${res.status}`)
return res.json()
}
// Subvenciones de tecnología en Cataluña con más de 100.000€
const subvenciones = await buscarSubvenciones({
ccaa: 'Cataluña',
sector: 'tecnologia',
importeMin: 100000,
})
console.log(`${subvenciones.length} convocatorias encontradas`)
subvenciones.forEach(s => {
console.log(`• ${s.convocatoria}`)
console.log(` Órgano: ${s.organoConvocante}`)
console.log(` Importe: ${s.importeTotal ? `${Number(s.importeTotal).toLocaleString('es-ES')}€` : 'No especificado'}`)
console.log(` Cierre: ${new Date(s.fechaResolucion).toLocaleDateString('es-ES')}`)
console.log(` Más info: ${s.urlBdns}`)
console.log()
})
Con Python
import requests
import os
from datetime import datetime
API_KEY = os.environ['APISPAIN_API_KEY']
BASE_URL = 'https://api.apispain.es/v1'
def buscar_subvenciones(ccaa=None, sector=None, importe_min=None):
params = {'abiertas': 'true'}
if ccaa: params['ccaa'] = ccaa
if sector: params['sector'] = sector
if importe_min: params['importeMin'] = importe_min
res = requests.get(
f'{BASE_URL}/bdns/subvenciones',
params=params,
headers={'Authorization': f'Bearer {API_KEY}'}
)
res.raise_for_status()
return res.json()
# Subvenciones de I+D en Andalucía
subvenciones = buscar_subvenciones(ccaa='Andalucía', sector='investigacion')
for s in subvenciones:
fecha_cierre = datetime.fromisoformat(s['fechaResolucion'].replace('Z', '+00:00'))
print(f"• {s['convocatoria']}")
print(f" Importe: {float(s['importeTotal'] or 0):,.0f}€")
print(f" Cierre: {fecha_cierre.strftime('%d/%m/%Y')}")
print(f" URL: {s['urlBdns']}")
print()
Sistema de alertas por email
El caso de uso más valioso: recibir un email automático cuando se abra una subvención relevante. Este script comprueba las novedades y envía alertas:
import nodemailer from 'nodemailer'
import fs from 'fs/promises'
const API_KEY = process.env.APISPAIN_API_KEY
// Configuración de alertas por perfil
const PERFILES = [
{
nombre: 'Startup Tecnología Madrid',
email: 'gestor@tuempresa.com',
filtros: { ccaa: 'Madrid', sector: 'tecnologia' },
},
{
nombre: 'Empresa Agroalimentaria Andalucía',
email: 'direccion@otraempresa.com',
filtros: { ccaa: 'Andalucía', sector: 'agricultura' },
},
]
async function cargarSubvencionesVistas() {
try {
const raw = await fs.readFile('subvenciones-vistas.json', 'utf-8')
return new Set(JSON.parse(raw))
} catch {
return new Set()
}
}
async function guardarSubvencionesVistas(vistas) {
await fs.writeFile('subvenciones-vistas.json', JSON.stringify([...vistas]))
}
async function revisarYNotificar() {
const vistas = await cargarSubvencionesVistas()
const nuevasIds = []
for (const perfil of PERFILES) {
const url = new URL('https://api.apispain.es/v1/bdns/subvenciones')
url.searchParams.set('abiertas', 'true')
Object.entries(perfil.filtros).forEach(([k, v]) => url.searchParams.set(k, v))
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` }
})
const subvenciones = await res.json()
const nuevas = subvenciones.filter(s => !vistas.has(s.bdnsId))
if (nuevas.length > 0) {
await enviarAlerta(perfil, nuevas)
nuevas.forEach(s => nuevasIds.push(s.bdnsId))
}
}
if (nuevasIds.length > 0) {
nuevasIds.forEach(id => vistas.add(id))
await guardarSubvencionesVistas(vistas)
console.log(`✅ ${nuevasIds.length} nuevas subvenciones notificadas`)
} else {
console.log('✅ Sin novedades')
}
}
async function enviarAlerta(perfil, subvenciones) {
// Configura tu SMTP aquí (o usa Resend, SendGrid, etc.)
const transporter = nodemailer.createTransport({ /* tu config */ })
const lista = subvenciones.map(s => `
<li>
<strong>${s.convocatoria}</strong><br/>
Órgano: ${s.organoConvocante}<br/>
Importe: ${s.importeTotal ? `${Number(s.importeTotal).toLocaleString('es-ES')}€` : 'No especificado'}<br/>
<a href="${s.urlBdns}">Ver en BDNS →</a>
</li>
`).join('')
await transporter.sendMail({
from: 'Alertas Subvenciones <alertas@tudominio.com>',
to: perfil.email,
subject: `${subvenciones.length} nueva(s) subvención(es) para ${perfil.nombre}`,
html: `<ul>${lista}</ul>`,
})
}
revisarYNotificar()
Ejecútalo con un cron cada 6 horas (que es la frecuencia de actualización del BDNS):
0 */6 * * * node /ruta/alertas-subvenciones.js >> /var/log/subvenciones.log 2>&1
Webhooks en tiempo real (plan Pro)
Si no quieres gestionar el cron tú mismo, el plan Pro incluye webhooks. Apispain ejecuta el rastreo y te notifica automáticamente:
import Apispain from 'apispain'
const client = new Apispain({ apiKey: process.env.APISPAIN_API_KEY })
const webhook = await client.webhooks.create({
url: 'https://tu-servidor.com/webhook/subvenciones',
secret: 'tu-secreto',
eventos: ['bdns.subvencion'],
filtros: {
ccaa: 'Madrid',
sector: 'tecnologia',
},
})
Subvenciones por beneficiario específico
Si buscas subvenciones para un tipo concreto de empresa:
// Subvenciones para autónomos en toda España
const autonomos = await fetch(
'https://api.apispain.es/v1/bdns/subvenciones?beneficiario=autonomos&abiertas=true',
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then(r => r.json())
// Subvenciones para startups
const startups = await fetch(
'https://api.apispain.es/v1/bdns/subvenciones?beneficiario=startup&abiertas=true',
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then(r => r.json())
Próximos pasos
- Consulta la documentación completa de la API BDNS
- Si también necesitas licitaciones públicas, el mismo cliente te da acceso con
client.place— guía de licitaciones - Ver todos los planes y precios
20 requests/mes gratis, sin tarjeta de crédito. API key lista al momento.