API de licitaciones públicas en España: guía completa

Guía completa para acceder a las licitaciones públicas de España (PLACE) mediante API REST. Filtra por CPV, importe y órgano contratante. Ejemplos en JavaScript, Python y curl.

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ódigoCategoría
72000000Servicios de tecnología de la información
72200000Servicios de programación y consultoría de software
45000000Trabajos de construcción
85000000Servicios de salud y asistencia social
79000000Servicios empresariales (consultoría, legal, RRHH)
60000000Servicios de transporte
55000000Hostelerí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ámetroTipoDescripción
cpvstringCódigo CPV (ej: 72000000)
estadostringpublicada, adjudicada, resuelta, cancelada
importeMinnumberImporte mínimo del contrato
importeMaxnumberImporte máximo del contrato
organostringTexto libre en el nombre del órgano contratante
fechaDesdestringPublicadas desde esta fecha (YYYY-MM-DD)

Recursos relacionados

Empieza gratis en 2 minutos

20 requests/mes gratis, sin tarjeta de crédito. API key lista al momento.

Ver documentación