Cenogram Demographics API
Demographic and economic indicators from GUS BDL (Central Statistical Office)
Demographic and economic indicators from GUS BDL (Central Statistical Office)
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/cenogram-pl:cenogram-pl-demographics-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Cenogram Demographics API
version: 1.0.0
description: 'Property and parcel intelligence for Poland. 8M+ verified transactions from the official RCN registry (Rejestr Cen Nieruchomości) — prices from notarial deeds, not asking/listing prices, from 2003 to present, nationwide. On top of prices: the picture we hold of a parcel — cadastral core, the deed history recorded against it, and context layers (flood hazard, general-plan zoning, building permits, structures, heritage, landslide hazard, subsurface constraints from mining terrains and groundwater reservoirs, surroundings, transit access, road access, agricultural use), plus municipality-level demographics and infrastructure signals, and a comparable-based valuation estimate. Context layers are sourced from official public registers. Transaction coverage is nationwide; the cadastral parcel corpus is near-complete but not the whole register and not live — every parcel collection carries a `corpus_coverage` block measuring what we hold for the counties it addresses. Access is metered in tokens: a new account starts with a token allowance and a trial, after which a free account keeps calling on a recurring free allowance, and paid plans raise it. Current allowances, trial length and prices live at https://cenogram.pl/api and are deliberately not repeated here, because a figure frozen into this description goes stale on its own. A 402 response carries the current balance and, on a free account, the date the next tokens arrive. Every operation states its price both in prose and machine-readably, in the `x-cenogram-token-cost` extension field, generated from the one pricing table. Tokens and credits are the same unit under two names: the response headers (`X-Credits-Cost`, `X-Credits-Balance`, `X-Credits-Refunded`) and some older endpoint descriptions say credits. Working with an AI assistant? Give it https://cenogram.pl/api/llms.txt — this whole surface as one plain-text file, costs, refund rules and response conventions included.'
servers:
- url: https://cenogram.pl
security:
- apiKey: []
tags:
- name: Demographics
description: Demographic and economic indicators from GUS BDL (Central Statistical Office)
paths:
/api/v1/demographics:
get:
summary: Demographic and economic indicators by location
tags:
- Demographics
description: 'Returns demographic, economic, housing, planning and other indicators from GUS BDL (Bank Danych Lokalnych / Central Statistical Office) for a TERYT administrative unit.
**TERYT level detection** (auto from code length): 2 digits = voivodeship (e.g. `14` Mazowieckie), 4 digits = county (e.g. `1465` Warszawa), 6 digits = municipality auto-resolved to its 7-digit form (e.g. `146501` → `1465011` Warszawa, `146510` → `1465108` Śródmieście), 7 digits = municipality addressed directly.
**The 7th digit is the TERYT unit type and it selects a different unit, not a spelling variant**: `1` urban gmina, `2` rural gmina, `3` urban-rural gmina as a whole, `4` / `5` the urban / rural part of an urban-rural gmina, `8` a district (dzielnica) of Warszawa. So `1465011` is Warszawa as one gmina, while its 18 districts are `1465028` (Bemowo) … `1465198` (Żoliborz), e.g. `1465108` Śródmieście. Pass the `…8` code for district-level indicators — the whole-gmina code returns city-wide figures. Likewise `…4` / `…5` split an urban-rural gmina into its town and its countryside, which the `…3` aggregate averages together.
**Hierarchy**: response always includes data from the requested level + all parent levels. A gmina query returns gmina + powiat + podregion (NUTS3) + województwo indicators.
**50 raw variables + 5 derived metrics** across 11 categories.
**Indicators by level**: L6 gmina has the richest dataset (~44 indicators: demographics, economy, housing, planning, infrastructure, environment, education). L5 powiat adds safety, re_market (~21 indicators). L4 podregion NUTS3 has economy_macro (GDP). L2 województwo has economy_macro + prices (~6 indicators).
**Derived metrics** (computed from raw variables): higher_education_pct, avg_new_apartment_size, developer_share_pct, residential_zoning_total_pct, property_crime_share_pct.
**Time series**: use yearFrom/yearTo for multi-year data. Each indicator returns values as year-keyed object. Without year params, returns latest available year per variable.
**Data freshness**: GUS BDL updates ~quarterly. Data cached for 24h. Census 2021 snapshot variables (education) update ~every 10 years.
Costs 1 token per call.'
parameters:
- schema:
type: string
example: Warszawa
in: query
name: location
required: false
description: City, county or gmina name. A city/county name resolves to a 4-digit county TERYT (powiat level); a gmina name resolves to that gmina (richer L6 indicators); a name shared by several units answers 400 with the candidates and their codes. Use this OR teryt. teryt wins when both are given.
- schema:
type: string
example: '1465011'
in: query
name: teryt
required: false
description: 'TERYT administrative code. Length determines level: 2 = voivodeship (e.g. 14), 4 = county (e.g. 1465), 6 or 7 = municipality (e.g. 146501, 1465011). In a 7-digit code the last digit is the TERYT unit type and picks a different unit: 1 urban, 2 rural, 3 urban-rural as a whole, 4/5 its urban/rural part, 8 a Warszawa district — so 1465011 is all of Warszawa and 1465108 is its Śródmieście district. Wins over location. Full list: https://eteryt.stat.gov.pl/'
- schema:
type: string
in: query
name: year
required: false
description: Return data for a specific year only (2003-current). Cannot be combined with yearFrom/yearTo.
- schema:
type: string
in: query
name: yearFrom
required: false
description: Start of year range for time series (min 2003). Use with yearTo for a closed range, or alone for open-ended (all years from N).
- schema:
type: string
in: query
name: yearTo
required: false
description: End of year range for time series (max current year + 1). Use with yearFrom for a closed range, or alone for open-ended (all years up to N).
- schema:
type: string
example: demographics,economy,economy_macro,housing,planning,infrastructure,environment,safety,re_market,education,prices
in: query
name: category
required: false
description: 'Filter indicators by category (comma-separated). Without this param, all available categories are returned. Valid: demographics, economy, economy_macro, housing, planning, infrastructure, environment, safety, re_market, education, prices'
x-cenogram-token-cost: 1
responses:
'200':
description: Demographic indicators for the requested location
content:
application/json:
schema:
type: object
description: Demographic indicators for the requested location
properties:
location:
type: object
properties:
name:
type:
- 'null'
- string
description: Location name from GUS (null when coverage is no_data)
country_code:
type: string
description: ISO 3166-1 alpha-2 country code. Always 'PL'.
location_type:
type: string
enum:
- city
- county
- municipality
- voivodeship
description: city = miasto na prawach powiatu; county = powiat ziemski; municipality = gmina; voivodeship = województwo
teryt:
type: string
description: TERYT code (7-digit for gmina, as resolved)
level:
type: string
enum:
- gmina
- powiat
- wojewodztwo
description: Administrative level (same values as indicators[].level)
hierarchy:
type: object
description: 'Parent administrative units (empty for województwo). Possible keys: powiat, podregion, wojewodztwo.'
propertyNames:
enum:
- powiat
- podregion
- wojewodztwo
additionalProperties:
type: object
properties:
teryt:
type:
- 'null'
- string
name:
type: string
coverage:
type: string
enum:
- full
- no_data
description: Data availability axis. 'full' = GUS data present; 'no_data' = the TERYT was addressed but GUS has no indicators for it (indicators is {}).
indicators:
type: object
description: Map of indicator slug → indicator data. Available slugs depend on TERYT level and category filter.
additionalProperties:
type: object
properties:
name:
type: string
description: Human-readable indicator name (Polish)
unit:
type: string
description: Measurement unit (%, zł, m², szt., ‰, tys., osoba, ha, lata)
variable_id:
type:
- 'null'
- integer
description: GUS BDL variable ID (null for derived metrics)
category:
type: string
enum:
- demographics
- economy
- economy_macro
- housing
- planning
- infrastructure
- environment
- safety
- re_market
- education
- prices
level:
type: string
enum:
- gmina
- powiat
- podregion
- wojewodztwo
description: Administrative level this data comes from
values:
type: object
description: Year → value map. Single entry for latest-year queries, multiple for time series (yearFrom/yearTo).
additionalProperties:
type: number
derived:
type: boolean
description: true if computed from other variables (not raw GUS data)
note:
type: string
description: Data quality note (e.g. structural zeros for MNPP cities)
snapshot:
type: boolean
description: true for Census 2021 data (next update ~2031)
snapshot_note:
type: string
description: Explanation of snapshot data source (e.g. 'Census 2021, next update ~2031')
meta:
type: object
properties:
variables_count:
type: integer
description: Number of indicators in this response
categories:
type: array
items:
type: string
enum:
- demographics
- economy
- economy_macro
- housing
- planning
- infrastructure
- environment
- safety
- re_market
- education
- prices
description: Categories present in the response
levels_included:
type: array
items:
type: string
enum:
- gmina
- powiat
- podregion
- wojewodztwo
description: Administrative levels with data in this response
data_source:
type: string
description: Always 'GUS BDL (Bank Danych Lokalnych)'
as_of:
type:
- 'null'
- string
description: Freshest data year across all indicators (e.g. '2024'); null when no data
notes:
type: array
items:
type: string
description: Resolution caveats (e.g. a district/delegatura TERYT narrowed to its parent county). Absent when there is nothing to note.
demo:
type: boolean
description: Present and true when in demo mode (limited indicators, no hierarchy)
'400':
description: Invalid input (bad TERYT format, invalid year, unknown category)
content:
application/json:
schema:
type: object
description: Invalid input (bad TERYT format, invalid year, unknown category)
properties:
error:
type: string
statusCode:
type: integer
message:
type: string
candidates:
type: array
description: Units whose name resembles the one asked for (at most 5), same row shape as /api/locations. Absent when none were found.
items:
type: object
properties:
code:
type: string
description: TERYT code of the unit
name:
type: string
typeName:
type:
- 'null'
- string
description: Municipality type, null for the other levels
level:
type: string
enum:
- voivodeship
- county
- municipality
- precinct
parent_name:
type:
- 'null'
- string
description: Name of the unit one level up
hint:
type: string
description: How to use `candidates`. Present only together with them.
'401':
description: Anonymous access not allowed — pass an API key, sign in, or use demo=true
content:
application/json:
schema:
type: object
description: Anonymous access not allowed — pass an API key, sign in, or use demo=true
properties:
error:
type: string
message:
type: string
reason:
type: string
signup_url:
type: string
docs:
type: string
'404':
description: No data found for this TERYT code in GUS BDL
content:
application/json:
schema:
type: object
description: No data found for this TERYT code in GUS BDL
properties:
error:
type: string
statusCode:
type: integer
message:
type: string
candidates:
type: array
description: Units whose name resembles the one asked for (at most 5), same row shape as /api/locations. Absent when none were found.
items:
type: object
properties:
code:
type: string
description: TERYT code of the unit
name:
type: string
typeName:
type:
- 'null'
- string
description: Municipality type, null for the other levels
level:
type: string
enum:
- voivodeship
- county
- municipality
- precinct
parent_name:
type:
- 'null'
- string
description: Name of the unit one level up
hint:
type: string
description: How to use `candidates`. Present only together with them.
operationId: getApiV1Demographics
x-operation-id-source: derived
components:
securitySchemes:
apiKey:
type: apiKey
name: X-Api-Key
in: header