Información general
  • Base URL: https://api.spainelectoralproject.com/v1
  • Versión: 1.0.0
  • Especificación: OpenAPI 3.1.0
  • Métodos HTTP: GET para consultas simples; POST en los endpoints de resultados para filtros complejos con listas largas (ver más abajo)
  • Autenticación: API key (header X-API-Key)
  • Formato respuesta: JSON (application/json)
Autenticación
Todos los endpoints de /v1/* requieren autenticación mediante API key, excepto /v1/auth/* (registro, verificación, gestión de clave) y /health.

Obtén tu clave registrándote en /v1/auth/register y verificando tu email. Incluye el header X-API-Key en todas tus peticiones:
curl -H "X-API-Key: TU_API_KEY" https://api.spainelectoralproject.com/v1/elecciones

Si la clave es inválida, revocada o falta, la API responderá con:

{
  "detail": "API key requerida"
}

o

{
  "detail": "API key inválida o revocada"
}
Spain Electoral Project — API

Endpoints

Health

MétodoRutaDescripción
GET/healthEstado de la aplicación y conexión a la BD

Elecciones

MétodoRutaDescripción
GET/v1/tipos-eleccionCatálogo de tipos de elección (array simple, sin paginación)
GET/v1/tipos-eleccion/{codigo}Detalle de un tipo de elección
GET/v1/eleccionesListado paginado de elecciones con filtros
GET/v1/elecciones/{id}Detalle de una elección con tipo expandido
GET/v1/elecciones/{id}/fuenteFuente oficial de los datos de una elección
GET/v1/elecciones/{id}/totales-territorioTotales territorio de una elección (paginado, con filtros)
GET/v1/elecciones/{id}/totales-territorio/{territorio_id}Resultado completo: totales + votos por partido

Territorios

MétodoRutaDescripción
GET/v1/territoriosListado paginado de territorios con filtros
GET/v1/territorios/{id}Detalle con todos los códigos
GET/v1/territorios/{id}/hijosHijos directos (navegación jerárquica)

Partidos

MétodoRutaDescripción
GET/v1/partidosListado paginado de partidos con filtros
GET/v1/partidos/{id}Detalle con agrupación (recode) expandida
GET/v1/partidos-recodeListado paginado de agrupaciones de partidos
GET/v1/partidos-recode/{id}Detalle de agrupación con lista de partidos asociados

Resultados

MétodoRutaDescripción
GET/v1/resultados/totales-territorioTotales territorio — filtros en query params
POST/v1/resultados/totales-territorioTotales territorio — filtros en body JSON (listas largas)
GET/v1/resultados/votos-partidoVotos por partido — filtros en query params
POST/v1/resultados/votos-partidoVotos por partido — filtros en body JSON (listas largas)
GET/v1/resultados/combinadosResultados combinados — filtros en query params
POST/v1/resultados/combinadosResultados combinados — filtros en body JSON (listas largas)

Paginación

Todos los endpoints que devuelven listados (excepto /v1/tipos-eleccion) usan paginación.

Parámetros

ParámetroTipoDefaultMínMáxDescripción
skipint00Registros a saltar
limitint501500Registros por página

Estructura de respuesta paginada

{
  "total": 254,
  "skip": 0,
  "limit": 50,
  "data": [...]
}
  • total: número total de registros que cumplen los filtros.
  • skip / limit: eco de los parámetros enviados.
  • data: array con los registros de la página actual.

Recorrido completo

Para recorrer todos los registros se incrementa skip en cada petición:

Página 1: ?skip=0&limit=100
Página 2: ?skip=100&limit=100
Página 3: ?skip=200&limit=100
...hasta que skip >= total

Cuando no hay coincidencias, la respuesta es total=0 y data=[] (HTTP 200, no es error).

Con POST: la paginación va dentro del body como campo paginacion, no como query params (ver POST en endpoints de resultados).

Filtros

Convenciones generales

  • Todos los filtros son opcionales. Sin filtros se devuelven todos los registros.
  • Los filtros de texto (nombre, siglas, denominacion, agrupacion) usan búsqueda parcial case-insensitive (ILIKE).
  • Para filtrar por múltiples valores, se repite el parámetro: ?tipo_eleccion=G&tipo_eleccion=A.

POST en endpoints de resultados

Los tres endpoints de resultados aceptan también el método POST en el mismo path. El comportamiento es idéntico al GET, pero los filtros se envían en el cuerpo JSON en lugar de en la query string.

¿Cuándo usar POST? Cuando necesitas filtrar por listas largas de municipios u otros identificadores. Las URLs con decenas de IDs superan los límites de longitud de proxies y servidores y producen errores 414 URI Too Long.

Estructura del body

Todos los campos son opcionales. Los campos null se pueden omitir.

{
  "paginacion": { "skip": 0, "limit": 200 },
  "eleccion_id": [208, 226],
  "territorio_id": null,
  "partido_id": [80, 73, 102],
  "year": ["2019", "2023"],
  "tipo_eleccion": ["G"],
  "tipo_territorio": ["municipio"],
  "codigo_ccaa": ["13"],
  "codigo_provincia": null,
  "codigo_municipio": ["28001", "28002", "28003", "..."]
}

partido_id solo está disponible en /votos-partido y /combinados, no en /totales-territorio.

La respuesta es exactamente la misma estructura PaginatedResponse que devuelve el GET.

Ejemplo con curl

curl -X POST https://api.spainelectoralproject.com/v1/resultados/combinados \
  -H "X-API-Key: TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paginacion": {"skip": 0, "limit": 500},
    "year": ["2023"],
    "tipo_eleccion": ["G"],
    "tipo_territorio": ["municipio"],
    "codigo_municipio": ["28001", "28002", "28003"]
  }'

Ejemplo con R (httr2)

library(httr2)

body <- list(
  paginacion    = list(skip = 0L, limit = 500L),
  year          = list("2023"),
  tipo_eleccion = list("G"),
  tipo_territorio = list("municipio"),
  codigo_municipio = as.list(mis_municipios)  # vector con todos los códigos
)

resp <- request("https://api.spainelectoralproject.com/v1/resultados/combinados") |>
  req_method("POST") |>
  req_headers(`X-API-Key` = Sys.getenv("ELECCIONESDB_API_KEY")) |>
  req_body_json(body) |>
  req_perform()

resultados <- resp |> resp_body_json(simplifyVector = TRUE)

Filtros por endpoint

/v1/elecciones

ParámetroTipoEjemploDescripción
tipo_eleccionstr (repetible)G, ACódigo del tipo de elección
yearstr (repetible)2019Año de la elección
messtr (repetible)04Mes (con cero a la izquierda)

/v1/territorios

ParámetroTipoEjemploDescripción
tipostr (repetible)ccaa, provinciaTipo de territorio
codigo_ccaastr (repetible)01Código de comunidad autónoma
codigo_provinciastr (repetible)28Código de provincia
nombrestrmadridBúsqueda parcial por nombre

/v1/partidos

ParámetroTipoEjemploDescripción
siglasstrpsoeBúsqueda parcial por siglas
denominacionstrsocialistaBúsqueda parcial por nombre completo

/v1/partidos-recode

ParámetroTipoEjemploDescripción
agrupacionstrPCE/IUBúsqueda parcial por agrupación

/v1/elecciones/{id}/totales-territorio

ParámetroTipoEjemploDescripción
territorio_idint20ID de territorio específico
tipo_territoriostr (repetible)provinciaTipo de territorio
codigo_ccaastr (repetible)01Código de comunidad autónoma
codigo_provinciastr (repetible)28Código de provincia
codigo_municipiostr (repetible)079Código de municipio

/v1/resultados/totales-territorio

ParámetroTipoEjemploDescripción
eleccion_idint (repetible)208ID de elección
territorio_idint (repetible)20ID de territorio
yearstr (repetible)2019Año de la elección
tipo_eleccionstr (repetible)GCódigo del tipo
tipo_territoriostr (repetible)provinciaTipo de territorio
codigo_ccaastr (repetible)01Código CCAA
codigo_provinciastr (repetible)28Código provincia
codigo_municipiostr (repetible)079Código municipio

/v1/resultados/votos-partido y /v1/resultados/combinados

Los mismos filtros que resultados/totales-territorio, más:

ParámetroTipoEjemploDescripción
partido_idint (repetible)9451ID de partido

Errores

Código HTTPSignificadoEjemplo
404Recurso no encontrado{"detail": "Elección no encontrada"}
414Query string demasiado largaVer abajo
422Error de validación{"detail": [{"type": "...", "loc": [...], "msg": "..."}]}

414 URI Too Long

Ocurre cuando el GET de un endpoint de resultados recibe una query string demasiado larga (más de ~4000 caracteres, equivalente a unas 40-50 repeticiones del parámetro codigo_municipio).

{
  "detail": "La query string es demasiado larga y puede causar errores en servidores o proxies intermedios. Usa el endpoint POST en el mismo path enviando los filtros como JSON en el cuerpo de la petición.",
  "post_endpoint": "/v1/resultados/combinados",
  "docs": "/docs"
}

Solución: usa el método POST en el mismo path con los filtros en el body JSON. Ver POST en endpoints de resultados.

404 Not Found

El body siempre tiene la forma {"detail": "Mensaje descriptivo"}. Mensajes posibles: "Elección no encontrada", "Territorio no encontrado", "Partido no encontrado", etc.

422 Validation Error

{
  "detail": [
    {
      "type": "greater_than_equal",
      "loc": ["query", "limit"],
      "msg": "Input should be greater than or equal to 1",
      "input": "-5"
    }
  ]
}

loc indica dónde está el error: ["query", "param"] para query params, ["body", "campo"] para campos del body POST.