La contratación pública en España mueve más de 200.000 millones de euros al año. Toda esa actividad — desde el mantenimiento de una carretera hasta el desarrollo de un sistema informático para un ministerio — se publica en PLACE (Plataforma de Contratación del Sector Público).
El problema de PLACE es el de siempre: buena fuente de datos, pésima interfaz para programadores. Búsqueda lenta, sin API pública documentada, exportaciones en formatos inconsistentes.
Esta guía te muestra cómo acceder a todas las licitaciones públicas de España desde tu código.
Conceptos clave antes de empezar
Qué es el código CPV
El CPV (Common Procurement Vocabulary) es el sistema de clasificación de contratos públicos de la Unión Europea. Todo contrato público tiene uno o varios códigos CPV que definen qué tipo de servicio o producto se contrata.
Ejemplos de códigos CPV relevantes:
| Código | Categoría |
|---|---|
72000000 | Servicios de tecnología de la información |
72200000 | Servicios de programación y consultoría de software |
45000000 | Trabajos de construcción |
85000000 | Servicios de salud y asistencia social |
79000000 | Servicios empresariales (consultoría, legal, RRHH) |
60000000 | Servicios de transporte |
55000000 | Hostelería y restauración |
Buscar por CPV es la forma más precisa de encontrar contratos relevantes para tu sector.
Qué campos incluye cada licitación
{
"id": "uuid",
"placeId": "C123456789",
"organoContratante": "Ministerio de Hacienda",
"objeto": "Servicio de desarrollo y mantenimiento de aplicaciones web",
"cpvCodigos": ["72200000", "72212000"],
"importe": "180000.00",
"importeConIva": "217800.00",
"estado": "publicada",
"adjudicatario": null,
"adjudicatarioNif": null,
"importeAdjudicacion": null,
"fechaPublicacion": "2026-04-01T00:00:00.000Z",
"fechaLimite": "2026-05-15T00:00:00.000Z",
"urlPlace": "https://contrataciondelestado.es/..."
}
Obtener tu API key
Ve a apispain.es, haz clic en Empezar gratis e introduce tu email. El plan Free incluye 20 requests/mes sin tarjeta.
Consultar licitaciones activas
Con curl
# Licitaciones de TI activas
curl "https://api.apispain.es/v1/place/licitaciones?cpv=72000000&estado=publicada" \
-H "Authorization: Bearer TU_API_KEY"
# Con rango de importe
curl "https://api.apispain.es/v1/place/licitaciones?cpv=72000000&importeMin=50000&importeMax=500000" \
-H "Authorization: Bearer TU_API_KEY"
# Por órgano contratante
curl "https://api.apispain.es/v1/place/licitaciones?organo=ministerio+hacienda&estado=publicada" \
-H "Authorization: Bearer TU_API_KEY"
Con JavaScript
const API_KEY = process.env.APISPAIN_API_KEY
async function buscarLicitaciones({ cpv, importeMin, importeMax, estado = 'publicada' } = {}) {
const url = new URL('https://api.apispain.es/v1/place/licitaciones')
if (cpv) url.searchParams.set('cpv', cpv)
if (importeMin) url.searchParams.set('importeMin', importeMin)
if (importeMax) url.searchParams.set('importeMax', importeMax)
if (estado) url.searchParams.set('estado', estado)
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` }
})
if (!res.ok) throw new Error(`Error ${res.status}`)
return res.json()
}
// Licitaciones de software entre 50k y 500k€
const licitaciones = await buscarLicitaciones({
cpv: '72200000',
importeMin: 50000,
importeMax: 500000,
})
licitaciones.forEach(l => {
const diasRestantes = Math.ceil(
(new Date(l.fechaLimite) - new Date()) / (1000 * 60 * 60 * 24)
)
console.log(`📄 ${l.objeto}`)
console.log(` Órgano: ${l.organoContratante}`)
console.log(` Importe: ${Number(l.importe).toLocaleString('es-ES')}€`)
console.log(` Plazo: ${diasRestantes} días para presentar oferta`)
console.log(` URL: ${l.urlPlace}`)
console.log()
})
Con Python
import requests
import os
from datetime import datetime
API_KEY = os.environ['APISPAIN_API_KEY']
def buscar_licitaciones(cpv=None, importe_min=None, importe_max=None, estado='publicada'):
params = {}
if cpv: params['cpv'] = cpv
if importe_min: params['importeMin'] = importe_min
if importe_max: params['importeMax'] = importe_max
if estado: params['estado'] = estado
res = requests.get(
'https://api.apispain.es/v1/place/licitaciones',
params=params,
headers={'Authorization': f'Bearer {API_KEY}'}
)
res.raise_for_status()
return res.json()
# Contratos de construcción activos
licitaciones = buscar_licitaciones(cpv='45000000', importe_min=100000)
for l in licitaciones:
fecha_limite = datetime.fromisoformat(l['fechaLimite'].replace('Z', '+00:00'))
dias = (fecha_limite - datetime.now(fecha_limite.tzinfo)).days
print(f"• {l['objeto'][:80]}...")
print(f" Importe: {float(l['importe'] or 0):,.0f}€ | Plazo: {dias} días")
print(f" URL: {l['urlPlace']}")
print()
Analizar adjudicaciones
Las licitaciones adjudicadas son igual de valiosas: te dicen quién gana los concursos en tu sector, a qué precios y con qué órganos.
// Adjudicaciones recientes en TI
const adjudicaciones = await buscarLicitaciones({
cpv: '72000000',
estado: 'adjudicada',
})
// Análisis de competencia
const porAdjudicatario = adjudicaciones.reduce((acc, l) => {
if (!l.adjudicatario) return acc
if (!acc[l.adjudicatario]) acc[l.adjudicatario] = { contratos: 0, volumen: 0 }
acc[l.adjudicatario].contratos++
acc[l.adjudicatario].volumen += Number(l.importeAdjudicacion || 0)
return acc
}, {})
// Ordenar por volumen adjudicado
const ranking = Object.entries(porAdjudicatario)
.sort(([, a], [, b]) => b.volumen - a.volumen)
.slice(0, 10)
console.log('Top 10 empresas por volumen adjudicado en TI:')
ranking.forEach(([empresa, datos], i) => {
console.log(`${i + 1}. ${empresa}`)
console.log(` ${datos.contratos} contratos — ${datos.volumen.toLocaleString('es-ES')}€`)
})
Pipeline completo: licitaciones relevantes en tu Slack
Este ejemplo integra la API con Slack para recibir diariamente las licitaciones nuevas de tu sector:
import cron from 'node-cron'
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL
const API_KEY = process.env.APISPAIN_API_KEY
// CPVs relevantes para una consultora tecnológica
const MIS_CPVS = ['72000000', '72200000', '72212000', '72260000']
async function notificarLicitacionesNuevas() {
const ayer = new Date()
ayer.setDate(ayer.getDate() - 1)
const ayerStr = ayer.toISOString().split('T')[0]
const todas = []
for (const cpv of MIS_CPVS) {
const url = new URL('https://api.apispain.es/v1/place/licitaciones')
url.searchParams.set('cpv', cpv)
url.searchParams.set('estado', 'publicada')
url.searchParams.set('fechaDesde', ayerStr)
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` }
})
const licitaciones = await res.json()
todas.push(...licitaciones)
}
// Deduplicar por ID
const unicas = [...new Map(todas.map(l => [l.id, l])).values()]
if (unicas.length === 0) return
const mensaje = unicas.map(l => {
const importe = l.importe ? `${Number(l.importe).toLocaleString('es-ES')}€` : 'Sin importe'
return `• *${l.objeto.slice(0, 80)}*\n ${l.organoContratante} | ${importe} | <${l.urlPlace}|Ver en PLACE>`
}).join('\n\n')
await fetch(SLACK_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `📄 *${unicas.length} licitación(es) nuevas en tu sector*\n\n${mensaje}`,
}),
})
}
// Ejecutar cada día a las 10:00
cron.schedule('0 10 * * *', notificarLicitacionesNuevas)
console.log('Monitor de licitaciones activo')
Webhooks para notificaciones en tiempo real
Con el plan Pro, en lugar de un cron puedes usar webhooks. Apispain rastreará PLACE y te notificará inmediatamente cuando aparezca una licitación nueva que coincida con tus filtros:
import Apispain from 'apispain'
const client = new Apispain({ apiKey: process.env.APISPAIN_API_KEY })
// Webhook para licitaciones de software > 100k€
const webhook = await client.webhooks.create({
url: 'https://tu-servidor.com/webhook/licitaciones',
secret: process.env.WEBHOOK_SECRET,
eventos: ['place.licitacion'],
filtros: {
cpv: '72200000',
importeMin: 100000,
},
})
console.log(`Webhook creado: ${webhook.id}`)
Tu endpoint recibirá un POST con los datos completos de la licitación:
// Express
app.post('/webhook/licitaciones', express.json(), (req, res) => {
const { evento, payload: licitacion } = req.body
// Verificar la firma
const firma = req.headers['x-apispain-signature']
// ... verificación con tu secret
console.log(`Nueva licitación: ${licitacion.objeto}`)
// Notifica a tu equipo, crea una oportunidad en tu CRM, etc.
res.sendStatus(200)
})
Referencia de filtros disponibles
| Parámetro | Tipo | Descripción |
|---|---|---|
cpv | string | Código CPV (ej: 72000000) |
estado | string | publicada, adjudicada, resuelta, cancelada |
importeMin | number | Importe mínimo del contrato |
importeMax | number | Importe máximo del contrato |
organo | string | Texto libre en el nombre del órgano contratante |
fechaDesde | string | Publicadas desde esta fecha (YYYY-MM-DD) |
Recursos relacionados
- Documentación completa de la API
- Guía: cómo monitorizar subvenciones del BDNS — si también necesitas subvenciones además de licitaciones
- API de licitaciones — referencia rápida
- Ver planes y precios
20 requests/mes gratis, sin tarjeta de crédito. API key lista al momento.