📡 PUBLIC REST API · DuckDB · 32M rows
REST API over
Peru's public budget.
An HTTP endpoint that accepts DuckDB SQL and returns JSON. Free access to the
mef_historico table with
137 columns and 32M rows (years 2013-2026). No auth, no API keys, no SDKs:
any HTTP-speaking tool can consume it.
https://app.gestionpublicaperu.com.pe/api/insights/query Available endpoints
Seven routes, all under https://app.gestionpublicaperu.com.pe.
No special headers required — only Content-Type: application/json for POST.
/api/insights/schema Metadata: 137 columns + types, min/max year, total rows, and an example query.
— /api/insights/query Executes a DuckDB query over mef_historico. SELECT/WITH only. Cap 10k rows, 30s timeout.
{ "sql": "SELECT ...", "limit": 1000 } /api/insights/slices/ Lists the precomputed JSON slices (kpis, evolution, sectors, category, departments, pliegos, meta).
— /api/insights/slices/{name}.json Serves a specific JSON slice. Same data the public CDN serves — useful for clients already talking to this origin.
— /api/insights/dimensions Distinct values for levels, funding sources, functions, departments, sectors and expense categories. Ideal to populate dropdowns. Cached 5 min.
— /api/insights/serie?sector=11 Annual PIM/Accrued/Execution % series with simple filters (sector, pliego, department, level, source, function). No SQL — just query params.
— /api/insights/top-sectores?anio=2025 Top sectors by PIM in a given year. Default = last closed year. Returns up to 33 rows with code, name, PIM, accrued spending and execution %.
— How to use it
Four variants for four audiences. Pick the one that fits.
🐚 curl (bash · terminal)
curl -X POST https://app.gestionpublicaperu.com.pe/api/insights/query \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT anio, ROUND(SUM(MTO_PIM)/1e9, 1) AS pim_mil_M FROM mef_historico WHERE SECTOR LIKE \"11%\" GROUP BY anio ORDER BY anio"
}' 🐍 Python (httpx · ideal for notebooks and scripts)
import httpx
r = httpx.post(
"https://app.gestionpublicaperu.com.pe/api/insights/query",
json={
"sql": """
SELECT anio,
ROUND(SUM(MTO_PIM)/1e9, 1) AS pim_mil_M,
ROUND(SUM(DEVENGADO_)*100
/ NULLIF(SUM(MTO_PIM),0), 1) AS avance_pct
FROM mef_historico
WHERE PLIEGO LIKE '036%' -- MTC (Ministry of Transport)
GROUP BY anio
ORDER BY anio
""",
},
timeout=60,
)
data = r.json()
print(data["columnas"]) # ['anio', 'pim_mil_M', 'avance_pct']
print(data["filas"]) # [[2013, 4.2, 87.5], [2014, 5.1, 89.1], ...] 🌐 JavaScript (fetch · browser or Node)
const r = await fetch("https://app.gestionpublicaperu.com.pe/api/insights/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
sql: `
SELECT DEPARTAMENTO_META,
ROUND(SUM(MTO_PIM)/1e6, 1) AS pim_M
FROM mef_historico
WHERE anio = 2025 AND DEPARTAMENTO_META != '00 '
GROUP BY DEPARTAMENTO_META
ORDER BY pim_M DESC
LIMIT 5
`,
}),
});
const { columnas, filas, n_filas } = await r.json();
console.table(filas); 📊 Excel · Power Query M
// Excel → Data → Get Data → From Other Sources → Blank Query → Advanced Editor
let
url = "https://app.gestionpublicaperu.com.pe/api/insights/query",
sql = "SELECT anio, ROUND(SUM(MTO_PIM)/1e9, 1) AS pim FROM mef_historico GROUP BY anio ORDER BY anio",
body = Json.FromValue([sql = sql]),
resp = Web.Contents(url, [
Content = body,
Headers = [#"Content-Type" = "application/json"]
]),
json = Json.Document(resp),
tabla = Table.FromRows(json[filas], json[columnas])
in
tabla In Excel: Data → Get Data → From Other Sources → Blank Query → Advanced Editor. Paste the block and refresh whenever you want fresh data.
Real use cases
Five frequent questions, with the SQL or REST call that answers them and a preview of the response.
How much did the Education sector grow between 2013 and 2025?
#1SQL:
SELECT anio,
ROUND(SUM(MTO_PIM)/1e9, 1) AS pim_mil_M,
ROUND(SUM(DEVENGADO_)/1e9, 1) AS dev_mil_M
FROM mef_historico
WHERE SECTOR LIKE '10%' -- 10: EDUCACION
GROUP BY anio
ORDER BY anio Output:
anio pim_mil_M dev_mil_M
2013 18.8 17.4
2025 41.1 36.8
→ +118% over 12 years Top 10 spending units (pliegos) by PIM in 2025
#2SQL:
SELECT PLIEGO,
ROUND(SUM(MTO_PIM)/1e9, 2) AS pim_mil_M
FROM mef_historico
WHERE anio = 2025
GROUP BY PLIEGO
ORDER BY pim_mil_M DESC
LIMIT 10 Output:
PLIEGO pim_mil_M
006. INSTITUTO PERUANO DE SEGURIDAD ... 14.92
036. M. DE TRANSPORTES Y COMUNICACIO... 12.40
010. M. DE EDUCACION 9.87
011. M. DE SALUD 8.54
... Execution rate by government level (2025)
#3SQL:
SELECT NIVEL_GOBIERNO,
ROUND(SUM(MTO_PIM)/1e9, 1) AS pim_mil_M,
ROUND(SUM(DEVENGADO_)*100
/ NULLIF(SUM(MTO_PIM),0), 1) AS avance_pct
FROM mef_historico
WHERE anio = 2025
GROUP BY NIVEL_GOBIERNO
ORDER BY pim_mil_M DESC Output:
NIVEL_GOBIERNO pim_mil_M avance_pct
E. GOBIERNO NACIONAL 180.4 78.3
R. GOBIERNOS REGION... 52.1 82.5
M. GOBIERNOS LOCALES 40.0 71.2 No SQL needed: annual Health series with a single GET
#4SQL:
curl "https://app.gestionpublicaperu.com.pe/api/insights/serie?sector=11"
# or try:
# /api/insights/serie?pliego=036 (Ministry of Transport)
# /api/insights/serie?departamento=15 (Lima)
# /api/insights/serie?funcion=20.%20SALUD
# /api/insights/serie?nivel=R.%20GOBIERNOS%20REGIONALES Output:
{
"filtros": { "sector": "11", ... },
"serie": [
{"anio": 2013, "pim_mil_M": 8.04, "dev_mil_M": 7.31, "avance_pct": 90.9, ...},
{"anio": 2025, "pim_mil_M": 14.52, "dev_mil_M": 14.15, "avance_pct": 97.4, ...}
]
}
→ Ideal when you don't want to write SQL. Monthly accrued spending of Health in Lima (2025)
#5SQL:
SELECT MTO_DEVENGA_01 AS ene, MTO_DEVENGA_02 AS feb,
MTO_DEVENGA_03 AS mar, MTO_DEVENGA_04 AS abr
FROM mef_historico
WHERE anio = 2025
AND SECTOR LIKE '11%' -- Health
AND DEPARTAMENTO_META LIKE '15.%' -- Lima Output:
Each row is a budget line; ideal for
GROUP BY + SUM if you want aggregates.
Returns up to 10,000 rows — narrow your
WHERE clause if your universe is large. Limits and rules
Designed for ad-hoc analysis, not for production critical applications. For now no auth, no rate limit — but with sensible safety caps.
🛡️
SELECT / WITH only
Any attempt at INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, COPY, PRAGMA, ATTACH and friends returns HTTP 400. The database is read-only.
📦
Cap of 10,000 rows
The backend wraps your query with LIMIT 10000.
If you need more, add a GROUP BY or paginate
with WHERE anio = ....
⏱️
30-second timeout
If the query takes longer, it returns HTTP 504. Most queries with
GROUP BY take under 1s, so this typically
only fires on unfiltered full scans.
🔓
No auth, no keys
Public endpoint. No visible rate limit. If you abuse it and crash the server, please reach out first — we'd hate to have to put up barriers.
Quick schema
The 15 most-used columns. For the full list of 137 columns and their types, see /api/insights/schema .
| Column | Type | Description |
|---|---|---|
anio | INT | Fiscal year (2013-2026). For the current year see es_parcial. |
es_parcial | BOOL | true if it is the current-year snapshot (live data). false if it is a closed year. |
SECTOR | VARCHAR | Format "NN: NAME" with a colon. Filter with LIKE 'NN%', never with =. |
PLIEGO | VARCHAR | Spending unit. Format "NNN. NAME" (with period). E.g. "036. M. DE TRANSPORTES...". |
SEC_EJEC | VARCHAR | Executing unit code — no leading zeros (e.g. "154", not "000154"). |
NIVEL_GOBIERNO | VARCHAR | E. GOBIERNO NACIONAL · R. GOBIERNOS REGIONALES · M. GOBIERNOS LOCALES. |
DEPARTAMENTO_META | VARCHAR | Format "NN. NAME" (with period). E.g. "15. LIMA". Filter out "00 " (empty) for valid geography only. |
FUENTE_FINANC | VARCHAR | Funding source (1=Ordinary Resources, 2=Collected, etc.). |
FUNCION | VARCHAR | Budget function (e.g. "22. EDUCACION", "20. SALUD"). |
CATEGORIA_GASTO | VARCHAR | 5 CURRENT SPENDING · 6 CAPITAL SPENDING · 7 DEBT SERVICE. |
GENERICA | VARCHAR | Classifier generic (e.g. "21" = Personnel). No periods when filtering. |
MTO_PIA | DOUBLE | Initial Institutional Budget (PIA) in soles. |
MTO_PIM | DOUBLE | Modified Institutional Budget (PIM) in soles — current ceiling. |
DEVENGADO_ | DOUBLE | Accrued spending year-to-date (soles). |
MTO_DEVENGA_01..12 | DOUBLE | 12 columns — monthly accrued spending by calendar month. |
💡 Tip: SECTOR uses the format "NN: NAME" (colon)
while PLIEGO and DEPARTAMENTO_META use
"NN. NAME" (period). Always filter with LIKE 'NN%' to avoid format issues.
Note: Column names and example values are kept in their original Spanish form — they are the official MEF nomenclature.
📖 Interactive OpenAPI spec
Explore every endpoint with a live Swagger UI, or download the JSON spec to feed into Postman, Insomnia, your code generator or your favorite LLM.
Building something with this?
We'd love to see it. Reach out at soporte@gestionpublicaperu.com.pe .