# INE (Instituto Nacional de Estadística) (`legaltech/ine`) Actor

Capa semántica en lenguaje natural para extraer datos del INE (Instituto Nacional de Estadística de España).

- **URL**: https://apify.com/legaltech/ine.md
- **Developed by:** [Miguel González](https://apify.com/legaltech) (community)
- **Categories:** Automation
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.05 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 📊 INE - Estadísticas de España (INEbase)

Actor de [Apify](https://apify.com) para consultar en **lenguaje natural** las estadísticas oficiales del **INE** (IPC, EPA, población, natalidad, migraciones, turismo, PIB, precios de vivienda...). Pensado para usarse desde un LLM vía el **MCP de Apify**: rellena el `input` JSON de una de las 7 acciones — nunca construyas tú mismo una URL de `servicios.ine.es` ni de `www.ine.es`, ni intentes hacer `fetch` directo al INE. Toda la interacción con el INE pasa por este actor.

### 🔑 Regla de oro: no inventes identificadores

`tableId`, `seriesCode`, `operationCode` y los ids dentro de `filters` (`"variableId:valorId"`) son códigos opacos del INE que no siguen ningún patrón adivinable. **Nunca los inventes.** Si no los tienes:

1. No conoces la tabla/operación → `action="search"` con el **tema**, no la pregunta completa del usuario.
2. Necesitas filtrar por territorio, sexo, edad, sector... y no conoces el id → `action="operation_variables"`.

Solo con esos ids en mano, pide los datos con `table_data` / `series_data` / `operation_data`.

### 🔀 Las 7 acciones (`action`)

| `action` | Para qué | Campos relevantes |
| --- | --- | --- |
| `search` | **Primer paso casi siempre.** Lenguaje natural → tablas candidatas (con `tableId` y la operación a la que pertenecen). No elige por ti: tú (el LLM) decides cuál usar después. | `query`, `maxResults` |
| `table_data` | Datos de **una tabla concreta** (todas sus series, o filtradas). | `tableId`, `filters`, `nult`/`dateFrom`/`dateTo` |
| `series_data` | Datos de **una serie temporal concreta** (cuando ya sabes el código exacto). | `seriesCode`, `nult`/`dateFrom`/`dateTo` |
| `operation_variables` | Lista las **variables** de una operación completa (sin `variableId`) o los **valores posibles** de una variable a nivel de TODA la operación (con `variableId`). ⚠️ Para variables con muchos valores (Municipios y similares) puede tardar 80-90 s: usa `table_dimension_values` si ya tienes una tabla concreta. | `operationCode`, `variableId` |
| `table_dimension_values` | Valores de una dimensión **dentro de una tabla concreta** (ej. los ~100 municipios de una tabla provincial, no los ~8.000 de toda España). Casi siempre preferible a `operation_variables` cuando ya tienes `tableId`. | `tableId`, `dimension` |
| `operation_data` | Datos filtrando por `variable:valor` **sin conocer ninguna tabla**. | `operationCode`, `filters` (obligatorio, **con una variable por cada dimensión que clasifica la serie**), `periodicity` (**obligatoria**) |
| `refresh_catalog` | Fuerza la reconstrucción del catálogo cacheado. Normalmente no hace falta llamarla: se autorrefresca sola. | — |

### 🔁 Flujo de decisión

```
¿El usuario ya te dio un tableId/seriesCode/operationCode en un turno anterior?
  SÍ → salta directo a table_data / series_data / operation_data.
  NO → action="search" con el TEMA (ej. "índice de precios de consumo", no
       "¿cómo ha subido el IPC?"). Incluye la sigla si el usuario la dio (IPC, EPA...).

De los candidatos de "search", ¿alguno tiene matchSource="both" y nombre inequívoco?
  SÍ → úsalo con table_data (tableId).
  Hay 2+ candidatos con el MISMO tableName → compara su campo "dimensions" antes de elegir.
  Ninguno encaja del todo → prueba maxResults más alto, o un término más genérico
       (el nombre de la operación: "Encuesta de Población Activa" en vez de
       "paro juvenil en Andalucía por trimestre").

¿El usuario pide un desglose (territorio/sexo/edad/sector...)?
  SÍ, y ya tienes un tableId concreto (de "search")
       → table_dimension_values con ese tableId y el nombre de la dimensión (ej. "Municipios")
       → devuelve valueId Y variableId juntos, listo para "filters". Rápido (~1-2 s).
  SÍ, y necesitas explorar a nivel de TODA la operación (sin tabla concreta aún)
       → operation_variables SIN variableId (localizas el id de esa dimensión)
       → operation_variables CON ese variableId (localizas el id del valor exacto)
       ⚠️ Para variables con muchos valores (Municipios, ~8.000 en toda España) esto es LENTO
          (80-90 s). Prefiere table_dimension_values siempre que tengas ya un tableId.
  ⚠️ No asumas que el id sirve para cualquier tabla de la operación: ver más abajo.

¿La consulta es sobre un MUNICIPIO concreto (ej. "Vélez-Málaga")?
  Las tablas de población municipal del INE están organizadas por PROVINCIA, no por municipio.
  Busca por la PROVINCIA ("población Málaga por municipios"), no por el nombre del municipio
  — buscar directamente "población de Vélez-Málaga" no encuentra la tabla.
```

### 📥 Entrada (Input)

| Campo | Tipo | Acción(es) | Descripción |
| --- | --- | --- | --- |
| `action` | `string` | todas | `search` | `table_data` | `series_data` | `operation_variables` | `operation_data` | `refresh_catalog`. |
| `query` | `string` | `search` | Tema en lenguaje natural (mejor "índice de precios de consumo" que una pregunta completa). Incluir la sigla si se conoce (IPC, EPA...) mejora mucho la precisión. |
| `maxResults` | `integer` | `search` | Máximo de tablas candidatas a devolver (por defecto 8). Súbelo si la tabla que necesitas no aparece. |
| `tableId` | `string` | `table_data` | Id numérico de tabla (campo `tableId` de un resultado de `search`). **Nunca inventarlo.** |
| `seriesCode` | `string` | `series_data` | Código de una serie concreta (ej. `IPC251856`). Suele salir del campo `seriesCode` de un `data_point` ya consultado, no de `search`. |
| `operationCode` | `string` | `operation_variables`, `operation_data` | Código alfabético de la operación (ej. `IPC`, `EPA`). Sale del campo `operationCode` de un resultado de `search`, o de la sigla si el usuario la menciona. |
| `variableId` | `string` | `operation_variables` | Id de una variable concreta, para listar sus valores posibles en vez de solo los nombres de las variables. Puede ser lento para variables con muchos valores (ver `table_dimension_values`). |
| `dimension` | `string` | `table_dimension_values` | Nombre de una dimensión de la tabla (campo `dimensions` de un resultado de `search` para ese `tableId`), ej. `"Municipios"`. Devuelve sus valores SOLO dentro de esa tabla — mucho más rápido que `operation_variables` para dimensiones con muchos valores. |
| `filters` | `array<string>` | `table_data` (opcional), `operation_data` (obligatorio) | Pares `"variableId:valorId"` (ej. `"115:29"` = provincia de Madrid). Valor vacío tras los dos puntos (`"115:"`) = todos los valores de esa variable. Los ids se obtienen con `operation_variables`/`table_dimension_values`, nunca se adivinan. En `operation_data`, incluye **una variable por cada dimensión que clasifica la serie** (ver errores a evitar). |
| `periodicity` | `integer` | `operation_data` (**obligatoria**) | 1=mensual, 3=trimestral, 6=semestral, 12=anual. Sin ella, `operation_data` no devuelve nada aunque los filtros sean correctos. |
| `nult` | `integer` | `table_data`, `series_data`, `operation_data` | Últimos N periodos por serie (por defecto 12). Alternativo a `dateFrom`/`dateTo`. |
| `dateFrom` / `dateTo` | `string` | `table_data`, `series_data` | Rango de fechas (`YYYY-MM-DD`). |
| `maxRows` | `integer` | `table_data`, `series_data`, `operation_data` | Corta la salida a N filas como máximo (por defecto 500), para no desbordar tu contexto — y porque el actor cobra por resultado devuelto: prefiere acotar con `filters`/`nult` antes que subir este límite. |
| `language` | `string` | todas | `ES` (por defecto) o `EN`. |

### 📤 Salida (Output)

Todos los items del dataset llevan un campo `itemType` que indica su forma.

#### `search` → `table_candidate`

```json
{
    "itemType": "table_candidate",
    "operationCode": "IPC",
    "operationName": "Índice de Precios de Consumo (IPC)",
    "tableId": 50913,
    "tableName": "Índices por comunidades autónomas: general y de grupos ECOICOP",
    "dimensions": ["Comunidades y Ciudades Autónomas", "Grupos ECOICOP", "Tipo de dato"],
    "periodicity": "Mensual",
    "yearStart": "2002",
    "matchScore": 8.5,
    "matchSource": "both",
    "ineTableUrl": "https://www.ine.es/jaxiT3/Tabla.htm?t=50913",
    "hint": "coincide por nombre de tabla Y aparece en el buscador de texto completo del INE (alta confianza). Dimensiones: Comunidades y Ciudades Autónomas, Grupos ECOICOP, Tipo de dato. Para obtener datos de esta tabla, vuelve a llamar con action=\"table_data\" y tableId=50913. ..."
}
```

- **`matchSource`**: `both` (máxima confianza: coincide por nombre y lo confirma el buscador de texto completo) · `web` (solo el buscador de texto completo lo relacionó — ej. consultas como "desempleo juvenil", donde esas palabras no aparecen en ningún nombre de tabla) · `catalog` (solo coincidencia de nombre). Con `matchSource="web"` a veces `matchScore` es `null`.
- **`dimensions`**: variables por las que se puede desglosar esa tabla. **Dos tablas pueden tener el `tableName` idéntico** y solo diferenciarse aquí (ej. una versión solo nacional vs. otra con desglose territorial); cuando `search` detecta nombres duplicados entre los candidatos, lo avisa en `hint`.

#### `search` → `related_document` (contexto adicional, no datos)

```json
{
    "itemType": "related_document",
    "title": "Informe enlace series de paro 1976-2000",
    "url": "https://www.ine.es/daco/daco42/daco4211/epa_reest_paro.pdf",
    "snippet": "Documento de trabajo Enlace de las series de paro 1976 2000 según la definición EPA 2002...",
    "modified": "jueves, 20 de octubre de 2005"
}
```

Hasta 5 notas de prensa / metodologías / fichas relacionadas con la consulta. Sin `tableId`/`seriesCode` ni datos numéricos: son contexto citable, no sustituyen a `table_data`/`operation_data`.

#### `table_data` / `series_data` / `operation_data` → `data_point`

Una fila por combinación serie × periodo:

```json
{
    "itemType": "data_point",
    "seriesCode": "IPC251856",
    "seriesName": "Nacional. Índice general. Variación anual. ",
    "tableId": 50902,
    "unit": "Tasas",
    "period": "M12",
    "year": 2025,
    "date": "2025-12-01T00:00:00.000+01:00",
    "value": 2.9,
    "dataStatus": "Definitivo",
    "labels": { "Totales Territoriales": "Nacional", "Índices y Tasas": "Variación anual" },
    "ineTableUrl": null,
    "apiUrl": "https://servicios.ine.es/wstempus/js/ES/DATOS_SERIE/IPC251856?tip=AM&det=2&nult=12"
}
```

`labels` trae el desglose real de esa fila (variable → valor) — es tu forma más fiable de confirmar qué contiene la fila sin adivinar. Para citar la fuente: `ineTableUrl` (visor de INEbase, enlace estable) si existe, si no `apiUrl`.

#### Sin resultados → `<acción>_no_results`

Cuando `table_data`, `series_data`, `operation_data`, `operation_variables` o `table_dimension_values` no obtienen filas/valores, el run sigue terminando `SUCCEEDED` (no es un fallo del actor), pero el dataset **nunca queda vacío en silencio**: se empuja un item explicando qué pasó y qué probar a continuación, en vez de dejarlo solo en los logs del run.

```json
{
    "itemType": "table_data_no_results",
    "tableId": "60127",
    "filters": ["141:274512"],
    "apiUrl": "https://servicios.ine.es/wstempus/js/ES/DATOS_TABLA/60127?tip=AM&det=2&nult=4&tv=141%3A274512",
    "message": "La tabla 60127 no devolvió datos para los filtros indicados (o no existe).",
    "hint": "Antes de asumir que no hay datos: (1) confirma que los filtros indicados son variableId:valueId de ESTA tabla con \"table_dimension_values\"...; (2) si los ids son correctos, prueba la misma combinación con action=\"operation_data\"..."
}
```

Los itemType equivalentes son `series_data_no_results`, `operation_data_no_results`, `operation_variables_no_results` y `table_dimension_values_no_results`. Trátalo como una señal para reintentar con otro `action` o revisar el filtro, no como "no existen esos datos en el INE": ver la nota sobre `DATOS_TABLA` más abajo.

#### `operation_variables` (sin `variableId`) → `variable`

```json
{ "itemType": "variable", "operationCode": "IPC", "variableId": 115, "variableName": "Provincias", "code": "PROV" }
```

#### `operation_variables` (con `variableId`) → `variable_value`

```json
{ "itemType": "variable_value", "operationCode": "IPC", "variableId": 115, "valueId": 29, "valueName": "Madrid" }
```

#### `table_dimension_values` → `table_dimension_value`

```json
{ "itemType": "table_dimension_value", "tableId": "2882", "dimension": "Municipios", "variableId": 19, "valueId": 2632, "valueName": "Vélez-Málaga", "code": "29094", "hint": "Usa \"19:2632\" en \"filters\" de action=\"table_data\" (tableId=2882) o action=\"operation_data\"." }
```

A diferencia de `variable_value` (que sale de `operation_variables`, a nivel de operación), este ya trae `variableId` listo para usar en `filters` sin necesidad de otra llamada.

#### `refresh_catalog` → `catalog_refresh`

```json
{ "itemType": "catalog_refresh", "operationsCount": 112, "tablesCount": 5521, "builtAt": "2026-08-12T10:00:00.000Z" }
```

### ⚠️ Errores a evitar (comprobados contra la API real)

- **Nunca inventes un id.** Si no lo tienes, `search` u `operation_variables` primero. Los códigos del INE son opacos: adivinarlos casi siempre falla.
- **`operation_data` sin `periodicity` no devuelve nada**, aunque el resto de la petición sea correcta. Rellénala siempre (1=mensual, 3=trimestral, 6=semestral, 12=anual).
- **Una operación puede tener varias variables para el mismo concepto**, y una tabla concreta solo usa una de ellas (comprobado en la EPA: "Edad" id 116, "Totales de edad" id 356, "Semiintervalos de edad" id 357 y "Grupos de edad" id 360 conviven en la misma operación, pero cada tabla usa solo una). `operation_variables` lista TODAS las variables de la operación, no las de una tabla en concreto: un id sacado de ahí puede no aplicar a la tabla que quieres filtrar, y el INE responde con un error si usas una variable que esa tabla no tiene. **Forma fiable de acertar:** llama a `table_data` con ese `tableId` SIN `filters` (o con `nult=1`), y mira el campo `labels` de las filas — ahí está el desglose real que trae la tabla.
- **Dos tablas pueden compartir el mismo `tableName`.** Compara `dimensions` antes de elegir entre ellas (`search` te avisa cuando detecta esto).
- **Una misma dimensión puede mezclar DOS variables distintas.** Comprobado: la tabla "Matrimonios por principales municipios..." tiene una dimensión llamada "Capitales y principales municipios" donde algunos valores son de la variable `115` (Provincias, ej. capitales de provincia) y otros de la `19` (Municipios, ej. "Vélez-Málaga"). **Nunca reutilices un `variableId` de otra tabla o de memoria** ("115 = territorio" no es una regla fija): pide siempre `variableId` y `valueId` JUNTOS de la misma llamada (`table_dimension_values` o `operation_variables`) para la tabla/operación que vas a consultar.
- **Con un solo filtro, `operation_data` puede tardar hasta 60-70 s y terminar vacío**, no solo lento (comprobado). El actor da hasta 100 s de margen para que la consulta pueda completarse en vez de fallar por timeout antes de tiempo, pero la solución real es añadir más filtros, no solo esperar.
- **Filtros incompletos en `operation_data` no siempre devuelven "todas las combinaciones" — a veces devuelven 0 resultados, sin error, y sin que el id esté mal.** Comprobado con DIR (tabla 301, "Locales por provincia, actividad y estrato"): filtrando solo Provincia + Actividad + Estrato (3 de las 5 variables que clasifican la serie) → 0 resultados; añadiendo también Condición jurídica y Tipo de dato (las 5) → 1 resultado. El actor ya compara `filters` contra la lista completa de variables de la operación (`VARIABLES_OPERACION`) y avisa por nombre de las que faltan antes de lanzar la consulta, y repite ese aviso si la respuesta viene vacía — pero la corrección (qué valor usar para cada variable que falta) sigue siendo cosa del llamante vía `operation_variables`.
- **Tablas muy cruzadas (varias dimensiones) pueden rechazar la petición con `"No puede mostrarse por restricciones de volumen"`** aunque el filtro y el rango de fechas sean razonables (comprobado con una tabla territorio × edad × edad × mes). El actor ya evita la causa más común (nunca manda `nult` y `date` a la vez, aunque `nult` tenga valor por defecto: combinarlos hace que el INE rechace tablas que con cualquiera de los dos por separado responden bien), pero si sigue pasando, añade un `filters` que acote al menos una dimensión más (`table_dimension_values`).
- **`DATOS_TABLA` (`table_data`) puede devolver, de forma puntual y transitoria, HTTP 200 con el cuerpo vacío para un filtro perfectamente válido** — no es que esa combinación no tenga datos (comprobado repitiendo la misma petición segundos después: la segunda vez responde bien). El actor ya reintenta automáticamente cuando detecta esto (`ineApi.js`), pero si el reintento se agota, `table_data` empuja un item `table_data_no_results` en vez de terminar en silencio con 0 items — nunca lo interpretes como "esta variable no existe en la tabla" sin antes probar de nuevo o, si conoces el `operationCode`, con `operation_data` sobre el mismo filtro.
- **`search` no siempre coloca la tabla más general en primer lugar.** El matching pondera las palabras según lo distintivas que son en el catálogo (ej. "turistas" pesa más que "extranjero", que aparece en cientos de tablas de temas distintos), pero sigue siendo texto, no comprensión semántica. Cuando una consulta combina varias palabras comunes que por casualidad coinciden con otra tabla no relacionada, esa puede rankear más alto. Revisa 2-3 candidatos, no asumas ciegamente el primero — usa `tableName`, `dimensions` y `matchSource` para decidir.
- **`operation_data` con menos filtros de los necesarios NO da error, pero el resultado es impredecible: puede tardar 30-40 s y devolver miles de series sin filtrar (comprobado con el IPC: un solo filtro de provincia, sin el grupo ECOICOP, tarda ~34 s), o puede devolver 0 resultados directamente (comprobado con DIR: 3 de sus 5 variables clasificadoras → 0 resultados; las 5 → 1 resultado).** Ninguno de los dos casos es un fallo del actor — es una consecuencia de no incluir un filtro por cada variable que clasifica la serie. El actor compara `filters` contra `VARIABLES_OPERACION` y avisa por nombre de las variables que faltan antes de consultar (y de nuevo si la respuesta viene vacía), pero la señal más fiable sigue siendo mirar tú mismo cuántas variables tiene la operación con `operation_variables`.
- **Para dimensiones con muchos valores (Municipios y similares), usa `table_dimension_values`, no `operation_variables`.** `operation_variables` con `variableId` de una variable como "Municipios" consulta TODA España (~8.000 valores) y el propio servidor del INE tarda 80-90 s en responder — comprobado, no es un problema de red puntual, es reproducible. Si ya tienes un `tableId` (de `search`), `table_dimension_values` consulta solo los valores DENTRO de esa tabla (ej. los ~100 municipios de una provincia) y responde en 1-2 s.
- **Los municipios se buscan por provincia, no por su propio nombre.** Las tablas de población municipal del INE están organizadas una por provincia (ej. "Málaga: Población por municipios y sexo"); buscar "población de Vélez-Málaga" no encuentra nada porque ningún nombre de tabla menciona ese municipio. Busca por la provincia ("población Málaga por municipios") y usa `table_dimension_values` para localizar el municipio concreto dentro de esa tabla.
- **Cita siempre la fuente** con `ineTableUrl` o `apiUrl` al construir una respuesta con estos datos.
- **No todo lo que hay en INEbase está en Tempus3.** Comprobado: no existe ninguna operación de comercio exterior de bienes (exportaciones/importaciones) en la API; si el usuario pide eso, dilo explícitamente en vez de devolver una tabla aproximada (ej. de Contabilidad Nacional) sin avisar. Antes de asumir que un tema no está cubierto, prueba `search` con 2-3 formulaciones distintas.
- **Algunos desgloses solo se llegan por `operation_variables`, no por `search`.** Ej. "IPC de alimentos": ninguna tabla del IPC se llama "alimentos" en su nombre (esa categoría es un VALOR dentro de la variable "Grupos ECOICOP", no el nombre de una tabla), así que `search` no la encuentra directamente — hay que buscar la tabla general del IPC y filtrar por ese valor con `operation_variables`/`filters`.

### 🤖 Ejemplos de flujo (lenguaje natural → acciones)

| El usuario pide... | Secuencia de acciones |
| --- | --- |
| "¿Cómo ha evolucionado el IPC general en España?" | `search` (query="Índice de Precios de Consumo") → `table_data` (tableId de "Índice general nacional") |
| "Dame la última tasa de paro" | `search` (query="Encuesta de Población Activa tasa de paro") → `table_data` con `nult=1` |
| "Población de Vélez-Málaga en los últimos 10 años" | `search` (query="población Málaga por municipios" — **por la provincia**, no por el municipio) → candidato con `dimensions: ["Municipios", "Sexo"]` → `table_dimension_values` (tableId, dimension="Municipios") para el id de "Vélez-Málaga" → `table_data` con `filters=["19:<id>"]` y `nult=10` |
| "¿Cómo está el desempleo juvenil?" | `search` (query="desempleo juvenil") → ninguna tabla se llama así, pero el buscador de texto completo del INE relaciona la consulta con la EPA (`matchSource="web"`) y devuelve la tabla "Tasas de paro por distintos grupos de edad, sexo y comunidad autónoma" → `operation_variables` para el id del grupo de edad joven → `operation_data` con ese filtro |
| "Compara el PIB de España y la UE" | `search` (query="Producto Interior Bruto") → si los candidatos no cubren la comparativa UE, dilo explícitamente en vez de improvisar un dato |

### 📝 Notas técnicas

- El catálogo (operaciones + tablas) se cachea y persiste entre ejecuciones del actor; se autorrefresca cada 7 días. Tiempos medidos contra el servicio real: catálogo en frío ~20-25 s (solo la 1ª vez o tras 7 días); `search` con caché ~2-3 s; `table_data`/`series_data`/`operation_variables` (variable normal) ~1-1,5 s; `table_dimension_values` ~1-2 s; `operation_data` bien filtrado ~1,5-5 s.
- **Dos operaciones son lentas por diseño, no por fallo:** `operation_variables` con una variable de muchos valores (Municipios, ~8.000 en toda España) puede tardar 80-90 s — usa `table_dimension_values` en su lugar siempre que tengas un `tableId`. `operation_data` con menos filtros de los que clasifican la serie puede tardar 30-40 s (devuelve todas las combinaciones sin filtrar en vez de fallar). El actor da más margen de tiempo a estas dos llamadas específicamente (hasta 60-100 s) y avisa cuando detecta el caso de un solo filtro, en vez de agotar el tiempo de espera en 3 reintentos cortos sin nunca completarse (~45 s) como ocurría antes.
- Todas las llamadas de datos piden a Tempus3 salida amigable + metadatos (etiquetas legibles, no solo códigos numéricos).
- `action="search"` combina el catálogo con una consulta al buscador de texto completo de INEbase; si esa consulta falla, `search` sigue funcionando solo con el catálogo (no rompe la ejecución).

# Actor input Schema

## `action` (type: `string`):

Qué quieres hacer. Casi siempre el flujo correcto es: (1) "search" con la pregunta del usuario en lenguaje natural para encontrar la tabla/operación, y (2) "table\_data" (o "operation\_data" si necesitas filtrar por territorio/sexo/edad...) para traer los datos numéricos de esa tabla. NO inventes un tableId/seriesCode/operationCode sin haberlo obtenido antes con "search" u "operation\_variables": los códigos del INE son opacos y adivinarlos casi siempre falla. Para dimensiones con MUCHOS valores dentro de una tabla concreta (ej. Municipios), usa "table\_dimension\_values" en vez de "operation\_variables": es mucho más rápido.

## `query` (type: `string`):

Describe en español lo que el usuario quiere encontrar, con el tema estadístico (no la pregunta completa): funciona mejor "índice de precios de consumo" o "población por comunidades autónomas" que una frase larga tipo "¿cómo ha evolucionado...?". Si el usuario ya conoce el nombre o siglas de la operación (IPC, EPA, PIB, Cifras de Población...), inclúyelo tal cual: mejora mucho la precisión.

## `maxResults` (type: `integer`):

Cuántas tablas candidatas devolver como máximo, ordenadas por relevancia. Súbelo si la primera búsqueda no trae la tabla que el usuario necesita.

## `tableId` (type: `string`):

Identificador numérico de la tabla del INE (campo "tableId" de un resultado de action="search"), ej. "50902". NUNCA lo inventes: si no lo tienes, ejecuta primero action="search".

## `seriesCode` (type: `string`):

Código de una serie temporal concreta del INE, ej. "IPC251856". Suele obtenerse de los metadatos de una tabla ya consultada, no de "search" (que trabaja a nivel de tabla). Úsalo solo cuando ya sepas exactamente qué serie individual quieres, en vez de todas las series de una tabla.

## `operationCode` (type: `string`):

Código alfabético de la operación estadística, ej. "IPC" (Índice de Precios de Consumo), "EPA" (Encuesta de Población Activa). Se obtiene del campo "operationCode" de un resultado de action="search", o de la propia sigla si el usuario la menciona.

## `variableId` (type: `string`):

Opcional. Sin rellenar, action="operation\_variables" lista TODAS las variables de la operación (ej. "Provincias", "Sexo", "Grupos ECOICOP") con su id. Rellénalo con el id de UNA de esas variables (campo "variableId" del resultado anterior) para obtener sus valores posibles y el id de cada uno (ej. valores de "Provincias" -> "Madrid"=29). Es el paso previo obligado para construir un filtro de "filters". ⚠️ Para variables con MUCHOS valores en toda España (la más notable: Municipios, ~8.000 valores), esto puede tardar 80-90 s — usa mejor action="table\_dimension\_values" con el tableId concreto que ya tengas, mucho más rápido.

## `dimension` (type: `string`):

Nombre de una de las dimensiones de la tabla indicada en "tableId" (campo "dimensions" de un resultado de action="search" para esa tabla, ej. "Municipios", "Sexo"). Devuelve los valores posibles de esa dimensión SOLO dentro de esa tabla (ej. los municipios de una provincia concreta, no los ~8.000 de toda España), lo que lo hace mucho más rápido que "operation\_variables" para dimensiones con muchos valores.

## `filters` (type: `array`):

Lista de filtros en formato "variableId:valorId" (ej. "115:29" = provincia de Madrid si 115 es el id de la variable Provincias y 29 el de Madrid). Los ids se obtienen con action="operation\_variables" — NUNCA los inventes. Deja el valorId vacío ("115:") para pedir TODOS los valores de esa variable en vez de uno concreto. En "operation\_data" cada filtro es obligatorio (así se localizan las series sin conocer una tabla); en "table\_data" son opcionales y sirven para acotar una tabla que ya trae muchas series mezcladas. ⚠️ Una operación puede tener varias variables para el mismo concepto (ej. la EPA tiene 4 variables de "edad" distintas) y una tabla concreta solo usa una: si el id no aplica a esa tabla, la llamada falla. Si dudas, llama antes a "table\_data" SIN filtros (o con nult=1) y mira el campo "labels" de las filas para ver el desglose real que usa esa tabla.

## `periodicity` (type: `integer`):

Id de periodicidad de las series a devolver: 1=mensual, 3=trimestral, 6=semestral, 12=anual. Comprobado contra la API real del INE: sin este valor, "operation\_data" (DATOS\_METADATAOPERACION) devuelve vacío aunque los filtros sean correctos. Consulta la periodicidad habitual de la operación (ej. el IPC es mensual=1, la EPA es trimestral=3) o mira "periodicity" en un resultado previo de action="search".

## `nult` (type: `integer`):

Cuántos periodos (los más recientes) devolver por serie. Aplica a table\_data, series\_data y operation\_data. Por defecto 12 para no devolver decenas de años de histórico de golpe; súbelo si el usuario pide una serie temporal larga, o mejor usa "dateFrom"/"dateTo" para un rango concreto (nult y las fechas son alternativos: si usas fechas, puedes dejar nult vacío).

## `dateFrom` (type: `string`):

Devuelve datos desde esta fecha en adelante (formato YYYY-MM-DD). Alternativa a "nult" cuando el usuario pide un rango de fechas concreto (ej. "desde 2020") en vez de "los últimos N datos".

## `dateTo` (type: `string`):

Devuelve datos hasta esta fecha (formato YYYY-MM-DD). Combínalo con "dateFrom" para acotar un rango, o úsalo solo para "hasta tal fecha".

## `maxRows` (type: `integer`):

Corta la salida a este número de filas (serie × periodo) como máximo, para no desbordar el contexto del LLM cuando una tabla trae muchas series — y para no facturar por resultados que no pediste (el actor cobra por fila devuelta). Acota mejor los filtros ("filters", "nult") en vez de subir mucho este límite.

## `language` (type: `string`):

Idioma de los nombres/etiquetas devueltos por el INE.

## Actor input object example

```json
{
  "action": "search",
  "query": "Índice de Precios de Consumo",
  "maxResults": 8,
  "operationCode": "IPC",
  "dimension": "Municipios",
  "filters": [],
  "nult": 12,
  "maxRows": 500,
  "language": "ES"
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "Índice de Precios de Consumo",
    "operationCode": "IPC",
    "dimension": "Municipios",
    "filters": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("legaltech/ine").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "Índice de Precios de Consumo",
    "operationCode": "IPC",
    "dimension": "Municipios",
    "filters": [],
}

# Run the Actor and wait for it to finish
run = client.actor("legaltech/ine").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "Índice de Precios de Consumo",
  "operationCode": "IPC",
  "dimension": "Municipios",
  "filters": []
}' |
apify call legaltech/ine --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,legaltech/ine"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/MrCSqeFB7AihmaeIB/builds/HvS1jo07HqHdeBp1F/openapi.json
