Saltar al contenido
Servicio independiente · No es un sitio oficial del BOEDatos a 30 de septiembre de 2026, 14:16 UTC
Subastas BOEAPI

Primeros pasos con la API de subastas del BOE

Guía de la API de subastas del BOE: pedir una ficha, decodificar sus bloques, leer las pujas y consultar solo lo que ha cambiado.

· 3 min de lectura

Esta guía recorre lo mínimo para sacar partido a la API: leer una subasta, entender la forma de los datos y montar un seguimiento que no descargue todo cada vez. Los ejemplos usan curl y Python, pero sirve cualquier cliente HTTP.

Necesitas la URL base y un token. Ambos se entregan con el acceso; aquí aparecen como $BASE y $TOKEN.

1. Pedir una subasta

El token va en la cabecera Authorization, tal cual, sin Bearer delante.

curl "$BASE/api/auctions/SUB-JA-2026-000000" \
  -H "Authorization: $TOKEN" \
  -H "Accept: application/json"

El identificador es el mismo que usa el portal. Si no existe en la base, la respuesta es un 404; si el token falla, un 401.

2. Decodificar los bloques de la ficha

Aquí está la única sorpresa de la API. Los bloques grandes de la ficha llegan como cadenas que contienen JSON, así que hay que decodificarlos una segunda vez:

import json
import os
import requests

BASE = os.environ["BASE"]
CABECERAS = {"Authorization": os.environ["TOKEN"], "Accept": "application/json"}

def ficha(identificador):
    r = requests.get(f"{BASE}/api/auctions/{identificador}", headers=CABECERAS, timeout=30)
    r.raise_for_status()
    subasta = r.json()
    for bloque in ("general_information", "managing_authorities",
                   "properties", "lots", "related", "bids"):
        if isinstance(subasta.get(bloque), str):
            subasta[bloque] = json.loads(subasta[bloque])
    return subasta

general_information es una lista de pares con una sola clave cada uno. Para consultarla con comodidad, conviértela en un diccionario:

subasta = ficha("SUB-JA-2026-000000")
general = {k: v for par in subasta["general_information"] for k, v in par.items()}

print(general["Tipo de subasta"])   # JUDICIAL EN VÍA DE APREMIO
print(general["Valor subasta"])     # 199.407,52 €

3. Convertir importes y fechas

Los importes son texto con formato español. Una función pequeña los convierte en números:

def euros(texto):
    """'199.407,52 €' -> 199407.52; devuelve None si no hay importe."""
    if not texto or not texto[0].isdigit():
        return None
    return float(texto.split(" ")[0].replace(".", "").replace(",", "."))

Campos como «Puja mínima» pueden traer un texto en lugar de un importe («Sin puja mínima»). La función devuelve None en ese caso.

Las fechas de la ficha incluyen su versión ISO entre paréntesis. Esa es la referencia exacta de la hora de cierre:

import re
from datetime import datetime

def iso(texto):
    m = re.search(r"ISO: ([0-9T:+\-]+)", texto or "")
    return datetime.fromisoformat(m.group(1)) if m else None

cierre = iso(general.get("Fecha de conclusión"))

4. Leer los bienes

Cada elemento de properties es un bien. Además de la descripción y de los pares que publica el portal, lleva superficie en metros cuadrados cuando el texto la indica.

for bien in subasta["properties"] or []:
    datos = {k: v for par in bien["property"] for k, v in par.items()}
    print(bien["title"], bien["superficie"], datos.get("Localidad"), datos.get("Provincia"))

La provincia y la localidad están ahí, dentro de cada bien. En las subastas por lotes, properties viene vacío y los bienes están en lots.

5. Seguir las pujas

Para vigilar una subasta abierta no hace falta pedir la ficha entera. Hay una ruta más ligera:

curl "$BASE/api/auctions/SUB-JA-2026-000000/bids" -H "Authorization: $TOKEN"
{
  "auction_number": "SUB-JA-2026-000000",
  "bids": { "tipo": "oculta", "importe": null, "mensaje": "La subasta ha recibido alguna puja. …", "lotes": [] },
  "bids_refreshed_at": "2026-09-30T12:56:03.000000Z"
}

Conviene tener claro qué se puede saber y qué no. Mientras la subasta está abierta, el portal no publica importes: solo dice si ha habido puja. Lo normal es ver sin_pujas, después oculta y, al concluir, puja_maxima_final con el importe. bids_refreshed_at indica cuándo se comprobó por última vez.

6. Pedir solo lo que ha cambiado

Consultar cien subastas cada hora para ver si alguna se ha movido es un desperdicio. El histórico de cambios responde a esa pregunta en una sola petición:

def cambios_desde(momento, campos=("fecha_conclusion", "bids.tipo", "bids.importe")):
    params = {"since": momento, "fields": ",".join(campos), "limit": 500}
    while True:
        r = requests.get(f"{BASE}/api/changes", headers=CABECERAS, params=params, timeout=30)
        r.raise_for_status()
        pagina = r.json()
        yield from pagina["changes"]
        if not pagina["has_more"]:
            break
        params["before_id"] = pagina["before_id_next"]

for c in cambios_desde("2026-09-30T00:00:00Z"):
    print(c["changed_at"], c["auction_number"], c["field"], c["old_value"], "->", c["new_value"])

Guarda el changed_at más reciente que hayas procesado y úsalo como since en la siguiente ejecución. Como el límite inferior es inclusivo, el último cambio puede repetirse: descártalo por su id.

Si solo te interesan unas subastas concretas, añade auction_numbers con hasta 50 identificadores separados por comas.

Un ciclo de trabajo razonable

  1. Una vez al día, recorre /api/auctions página a página para incorporar las subastas nuevas.
  2. Cada hora, llama a /api/changes con since para enterarte de primeras pujas, prórrogas y cierres.
  3. Cuando una subasta concluya, pide su ficha una última vez para guardar el resultado.

Con eso tienes los mismos datos que quien revisa el portal a mano, sin revisarlo. La referencia completa de parámetros y respuestas está en la documentación, y si quieres entender por qué cambia la hora de cierre, en este artículo sobre las prórrogas.