Skip to main content

ine-api

Cliente Python tipado (sync + async) para la API Tempus del INE (Instituto Nacional de Estadística de España).

PyPI Python License CI typed


Estado

v1.0 — API pública estable. Cliente Python tipado (sync + async) para la API Tempus del INE, con cobertura completa del catálogo (32 endpoints; ver Cobertura). Publicado en PyPI; versión instalada: ine.__version__.


Instalación

Requiere Python ≥ 3.12.

pip install ine-api

o con uv:

uv add ine-api

Desarrollo / contribución:

git clone https://github.com/juanmicl/ine-api.git
cd ine-api
uv sync          # instala runtime + dev en un .venv aislado
uv run pytest    # corre los tests

Quickstart (sync)

from ine import Client, Lang

with Client(lang=Lang.ES) as client:
    operaciones = client.operaciones.list()
    for op in operaciones[:5]:
        print(op.id, op.codigo, op.nombre)

    # Últimas 12 observaciones de la serie IPC53262.
    # Troceamos en local: en vivo, `nult` puede devolver cuerpos vacíos
    # para algunas series del INE.
    datos = client.datos.serie("53262")
    for serie in datos:
        for obs in serie.data[-12:]:
            print(obs.fecha.isoformat(), obs.valor)

Client es un gestor de contexto: cierra la conexión HTTP al salir del bloque. Las respuestas se validan con pydantic (Operacion, DatosSerie, ...).


Quickstart (async)

Para aplicaciones que ya usan asyncio (FastAPI, crawlers, etc.):

import asyncio

from ine import AsyncClient, Lang


async def main() -> None:
    async with AsyncClient(lang=Lang.ES) as client:
        operaciones = await client.operaciones.list()
        for op in operaciones[:5]:
            print(op.id, op.codigo, op.nombre)

        # Últimas 12 observaciones (troceamos en local; ver nota en el quickstart sync).
        datos = await client.datos.serie("53262")
        for serie in datos:
            for obs in serie.data[-12:]:
                print(obs.fecha.isoformat(), obs.valor)


asyncio.run(main())

AsyncClient es el espejo asíncrono de Client: misma API, pero cada método es una coroutine que se espera con await.


¿Por qué existe?

La API Tempus del INE tiene varias rarezas que un wrapper ingenuo no maneja correctamente. Este cliente las traduce a excepciones tipadas y a modelos pydantic:

  1. HTTP 200 con cuerpo de error. El INE responde 200 OK con el body "La operación indicada no existe (X)" (un string JSON) cuando un recurso lógico no existe. raise_for_status() no lo detecta → el cliente lo traduce a INELogicalError.
  2. Redirecciones al resolver códigos. Al pedir por código alfanumérico (p. ej. IPC), la API hace un 301 al Id numérico. El cliente sigue redirecciones por defecto.
  3. Los 404 devuelven HTML, no JSON. Un recurso inexistente responde 404 con una página HTML. El cliente lo detecta por estado y por content-type y lo traduce a INENotFoundError / INEParseError.
  4. Tablas demasiado grandes. El INE rechaza ciertas tablas (p. ej. el Padrón) con un 200 de "restricciones de volumen". El cliente lo traduce a INEVolumeError y ofrece download_table() como vía alternativa (fichero oficial CSV/PC-Axis/XLSX).

Además, el esquema del INE es irregular (claves PascalCase, FK_/T3_, Fecha como epoch en ms, campos opcionales inconsistentes). Los modelos pydantic normalizan las claves a snake_case y ofrecen raw=True como válvula de escape cuando el esquema cambia.


Manejo de errores

Todas las excepciones heredan de INEError, así que basta un except INEError para capturar cualquier fallo de la librería.

Excepción Cuándo
INEError Raíz de la jerarquía. Base para cualquier fallo.
INEConnectionError Red / timeout / DNS / reset de conexión.
INEHTTPError Respuesta HTTP 4xx/5xx. Expone .status, .url y .body.
INENotFoundError Recurso no encontrado (HTTP 404). Subclase de INEHTTPError.
INELogicalError El INE respondió 200 con un mensaje de error lógico (rareza nº 1).
INEVolumeError Subclase de INELogicalError: la tabla es demasiado grande ("restricciones de volumen"). Usa download_table.
INEParseError La respuesta no es JSON o no tiene la forma esperada.
from ine import Client
from ine.errors import INEConnectionError, INENotFoundError, INEError

with Client() as client:
    try:
        datos = client.datos.serie("0")  # id inválido
    except INENotFoundError:
        print("La serie no existe.")
    except INEConnectionError:
        print("No se pudo contactar con el INE (red).")
    except INEError as err:
        print(f"Otro fallo: {err}")

Importa las excepciones desde ine.errors:

from ine.errors import (
    INEError,
    INEConnectionError,
    INEHTTPError,
    INENotFoundError,
    INELogicalError,
    INEVolumeError,
    INEParseError,
)

Parámetros del INE

Varios métodos aceptan estos parámetros de query del INE (todos opcionales):

Parámetro Valores válidos Significado
det "0" / "1" / "2" Nivel de detalle (básico / detallado / muy detallado).
tip "A" / "M" / "AM" Tipo de respuesta: amigable / metadatos / ambos.
nult int Devuelve los nult últimos datos o periodos.
p "1" / "3" / "6" / "12" Periodicidad: mensual / trimestral / bianual / anual.
date ["aaaammdd:aaaammdd"] Rango de fechas (el final es opcional: aaaammdd:).
tv ["id_variable:id_valor", ...] Filtros variable:valor (repetibles).
filtros list[(var, [valores])] → param g Grupos OR (mismo grupo) / AND (grupos distintos).
geo "0" / "1" Ámbito geográfico: 0 nacional / 1 desagregado (CCAA, provincias, municipios).
clasif int Restringe a una clasificación concreta (en valores.by_variable).
page int Página de un listado paginado (hasta 500 elem./página).
raw bool Si es True, devuelve el dict crudo del INE (sin modelo).

Filtros g (parámetro filtros): una lista de grupos (variable, valores).

  • Varios valores en un mismo grupo → OR (g1=["115:29","115:30"]).
  • Varios grupos → AND (g1=... + g2="3:84").
  • valores=None → todos los valores de esa variable (g3="762:").
client.datos.metadata_operacion(
    "IPC", p="1", nult=12,
    filtros=[("115", ["29", "30"]), ("3", ["84"])],
)

Cobertura de endpoints

Soportados (32 — catálogo completo)

Dominio Método (namespace) Recurso
OPERACIONES client.operaciones.list() OPERACIONES_DISPONIBLES
client.operaciones.get(id) OPERACION/{id}
SERIES client.series.get(id) SERIE/{id}
client.series.by_operacion(op) SERIES_OPERACION/{op}
client.series.by_tabla(id) SERIES_TABLA/{id}
client.series.valores(id) VALORES_SERIE/{id}
client.series.metadata_operacion(op, filtros=...) SERIE_METADATAOPERACION/{op}
DATOS client.datos.tabla(id) DATOS_TABLA/{id}
client.datos.serie(id, ...) DATOS_SERIE/{id}
client.datos.metadata_operacion(op, filtros=...) DATOS_METADATAOPERACION/{op}
MAESTROS client.maestros.escalas() ESCALAS
client.maestros.escala(id) ESCALA/{id}
client.maestros.unidades() UNIDADES
client.maestros.unidad(id) UNIDAD/{id}
client.maestros.unidades_operacion(op) UNIDADES_OPERACION/{op}
client.maestros.periodo(id) PERIODO/{id}
client.maestros.periodicidades() PERIODICIDADES
client.maestros.periodicidad(id) PERIODICIDAD/{id}
client.maestros.clasificaciones() CLASIFICACIONES
client.maestros.clasificaciones_operacion(op) CLASIFICACIONES_OPERACION/{op}
PUBLICACIONES client.publicaciones.publicaciones() PUBLICACIONES
client.publicaciones.publicaciones_operacion(op) PUBLICACIONES_OPERACION/{op}
client.publicaciones.publicacion_fecha(id) PUBLICACIONFECHA_PUBLICACION/{id}
VARIABLES client.variables.variables() VARIABLES
client.variables.variables_operacion(op) VARIABLES_OPERACION/{op}
client.variables.variable(id) VARIABLE/{id} (no documentado)
VALORES client.valores.by_variable(id_var) VALORES_VARIABLE/{id_var}
client.valores.by_variable_operacion(id_var, op) VALORES_VARIABLEOPERACION/{id_var}/{op}
client.valores.hijos(id_var, id_valor) VALORES_HIJOS/{id_var}/{id_valor}
TABLAS client.tablas.by_operacion(op) TABLAS_OPERACION/{op}
client.tablas.grupos(id) GRUPOS_TABLA/{id}
client.tablas.valores_grupo(id, id_grupo) VALORES_GRUPOSTABLA/{id}/{id_grupo}

Incluye 8 endpoints no documentados en el OpenAPI oficial (7 en client.maestros

  • client.variables.variable), descubiertos empíricamente y verificados en vivo.

Cobertura completa de la API Tempus del INE: los 32 endpoints del catálogo (24 del spec oficial + 8 no documentados).


Configuración

Todos los parámetros del constructor son keyword-only (la firma es estable entre versiones):

from ine import Cache, Client, Lang

client = Client(
    lang=Lang.ES,                  # idioma de los textos de la respuesta
    base_url="https://servicios.ine.es",  # host del servicio Tempus
    timeout=10.0,                  # timeout por petición, en segundos
    follow_redirects=True,         # seguir redirecciones (el INE hace 301 al resolver códigos)
    retries=3,                     # reintentos sobre GET idempotente (red + 429 + 5xx)
    headers={"X-Custom": "..."},   # cabeceras extra
    cache=Cache(ttl=300),          # cache en memoria opt-in (None = sin cache, por defecto)
    httpx_client=None,             # cliente httpx inyectado (DI para tests/config avanzada)
)
  • lang — Lang.ES|EN|CA|GL|EU. Determina el segmento /js/{lang}/ de las URLs y el idioma de los textos.
  • retries — reintentos automáticos sobre GET idempotente ante errores de red y 429/5xx, con backoff y respeto a Retry-After. 0 los desactiva. Solo aplica cuando el cliente construye su propio httpx.Client.
  • httpx_client — inyección de dependencias: si pasas tu propio httpx.Client, se respeta tal cual (sin reintentos ni cabeceras propias). Útil para tests (con respx) o para configuración avanzada del transporte.

El gestor de contexto cierra la conexión HTTP al salir:

with Client() as client:   # abre
    ...
# cierra automáticamente

Cache (opt-in)

Para no repetir peticiones idénticas al INE dentro de un mismo proceso (menos latencia y carga), activa un cache en memoria con TTL pasando un objeto Cache. Por defecto está desactivado (cache=None) — nunca te servirá datos stale sin que tú lo pidas:

from ine import Cache, Client

with Client(cache=Cache(ttl=300)) as client:   # cachea 5 min
    a = client.series.by_operacion("IPC")     # → petición HTTP
    b = client.series.by_operacion("IPC")     # → cache (0 peticiones)
  • Cache(*, ttl=300, maxsize=None) — ttl en segundos; maxsize opcional (evicción FIFO cuando se alcanza).
  • Solo se cachean las respuestas válidas; los errores (INEError) se relanzan siempre (nunca se cachean).
  • Es memoria por proceso (se pierde al cerrar). La misma instancia Cache puede compartirirse entre varios Client (sync y async).
  • El modelado pydantic se re-ejecuta sobre el dato cacheado (barato); si necesitas el JSON crudo sin re-validar, usa raw=True.

Descarga de ficheros (CSV / PC-Axis / XLSX)

Para tablas muy grandes que la API JSON rechaza ("restricciones de volumen", p. ej. el Padrón, id 68535) o cuando necesitas el formato oficial, descarga el fichero directamente. Es un servicio distinto (ine.es/jaxiT3/files, no la API Tempus JSON) y la descarga es por streaming:

from ine import Client, Format

with Client() as client:
    # Streama por chunks a fichero (seguro para decenas de MB) → devuelve Path
    path = client.download_table("68535", fmt=Format.CSV_BDSC, path="padron.csv")

    # O a bytes en memoria (cuidado con tablas muy grandes)
    data = client.download_table("68535", fmt=Format.PX)   # → bytes
  • Format: CSV_BDSC (CSV con cabecera, separador ;), CSV_BD, PX (PC-Axis), XLSX.
  • download_table vive en la raíz del Client (no bajo un namespace) porque es un servicio distinto al de la API JSON.
  • path dado → streama al fichero y devuelve pathlib.Path; path=None → bytes (se carga entero en memoria).
  • lang por defecto es el del cliente; los bytes son crudos (el charset del INE es inconsistente: declara ISO-8859-15 pero lleva BOM UTF-8).
  • Errores: INENotFoundError (404), INEHTTPError, INEConnectionError.
  • Cuándo usar esto vs client.datos.tabla: para tablas normales, datos.tabla es mejor (filtrable con nult/date/tv, tipado). download_table es la salida para tablas bloqueadas por volumen o cuando quieres el fichero oficial.

Pandas (opcional)

Para análisis, exporta las observaciones de una serie a un DataFrame de pandas. Es una dependencia opcional — el core no depende de pandas:

pip install ine-api[dataframe]
from ine import Client

with Client() as c:
    series = c.datos.serie("53262")   # -> list[DatosSerie]
    df = series[0].to_dataframe()      # -> pd.DataFrame (requiere el extra `dataframe`)
    # columnas: fecha, valor, anyo, fk_periodo, secreto

Si pandas no está instalado, to_dataframe() lanza un ImportError con la pista de instalación.


Licencia

  • El código de este cliente está bajo la licencia MIT (ver LICENSE).
  • Los datos del INE distribuidos a través de su API están bajo la licencia Creative Commons Attribution 4.0 (CC BY 4.0). Al usarlos debes atribuirlos al INE (Instituto Nacional de Estadística). Consulta los términos de reutilización del INE.

Esta librería no está afiliada al INE. Es un cliente de la comunidad.


Contribuir

Las contribuciones son bienvenidas. Configuración del entorno:

uv sync

Los gates de CI que debe pasar cualquier cambio son:

uv run ruff check . && uv run mypy ine && uv run pytest

Para errores, ideas o contribuciones, abre un issue en GitHub.

Metadata

Release files for ine-api 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ine-api 1.0.0
File Size Uploaded Provenance
ine_api-1.0.0.tar.gz 79.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ine-api 1.0.0
File Interpreter ABI Platform
ine_api-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 122.3 kB

Release files / ine_api-1.0.0.tar.gz

Download URL ine_api-1.0.0.tar.gz
Size 79.8 kB
Tags Source
SHA-256 checksum
How to use checksums
34c451c138a3842f05b6f37f187844ee0a4f1368be8a142725fa86ac5c5a9473
BLAKE2b-256 checksum
How to use checksums
d407926088661de2a9461fb7b2c90124113b755fe567e556b5d45a181ffe40a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.

Transparency log

Release files / ine_api-1.0.0-py3-none-any.whl

Download URL ine_api-1.0.0-py3-none-any.whl
Size 42.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
08a32361d178f2c9ae9761eee27140dff396d4ab8eea84650b00392c3689ed34
BLAKE2b-256 checksum
How to use checksums
08712975498aea94272f5defbb9cd5e4d6fb77710a8a86911cd796731ac6b8f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.

Transparency log

Security for ine-api 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ine_api-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page