Brazil Real Estate Transactions & Prices: São Paulo, Rio, BH
Pricing
from $10.00 / 1,000 transaction rows
Brazil Real Estate Transactions & Prices: São Paulo, Rio, BH
Official Brazil property transaction records (ITBI) for São Paulo, Rio de Janeiro and Belo Horizonte: ~3.9M declared values and price per m² in BRL, USD and EUR, monthly neighbourhood price statistics and an experimental São Paulo price index. API and AI-agent (MCP) ready.
Pricing
from $10.00 / 1,000 transaction rows
Rating
0.0
(0)
Developer
Lucas Huber Pissaia
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
2 days ago
Last modified
Categories
Share
What does it do?
Returns official Brazilian real-estate transaction records (the municipal property transfer tax, ITBI) for São Paulo, Rio de Janeiro and Belo Horizonte in one clean English schema: about 3.9 million transactions with declared values, built area and price per m², monthly price and liquidity statistics by city, district and neighbourhood, and an experimental repeat-sales price index for São Paulo. Values come in BRL, USD, EUR and inflation-adjusted BRL. Built on the municipalities' open data; one row per transaction. Use it as an API, schedule it, or call it as an MCP tool from an AI agent (see "Use with AI agents (MCP)").
Data last updated: 15 October 2026 (source files: São Paulo 8 Oct 2026, Rio 14 Oct 2026, Belo Horizonte 5 Oct 2026; the RUN_SUMMARY of every run carries data_build_date). Refreshed monthly.
Try before you pay: the Belo Horizonte data (514,430 rows, CC-BY) is available as a free dataset on Hugging Face: https://huggingface.co/datasets/pracadata/brazil-itbi-belo-horizonte. Check the schema and the quality there, then come back for São Paulo, Rio de Janeiro, the statistics and the API. This Actor adds São Paulo and Rio de Janeiro, the monthly area statistics for all three cities and the experimental São Paulo index. Field-by-field definitions: data dictionary.
What is ITBI? Imposto sobre a Transmissão de Bens Imóveis is the municipal tax paid when a property changes hands in Brazil. Each city publishes the transactions on which it was paid, with the value declared by the parties (a different signal from asking prices). Amounts are in Brazilian reais (BRL), nominal.
It does not look up properties: there is no address, street, number, tax-id, registry or coordinate search, and no row carries an address or a property id (see "Not supported" below, and the privacy note in the FAQ).
Use cases
- Proptech and valuation models: train or benchmark price-per-m² models on declared transaction values, not asking prices.
- Market and investor research: monthly price and liquidity by neighbourhood in three large cities; compare São Paulo, Rio and Belo Horizonte on one schema.
- Lenders, insurers and appraisers: a transaction-based reference for collateral and risk work.
- Academic and policy work: long series (São Paulo from 2006, Belo Horizonte from 2008, Rio from 2011) of what was actually declared at the tax office.
- AI agents: answer "what do apartments sell for in Pinheiros?" with a tool call instead of three spreadsheets.
How it compares with the free sources
Each city publishes its own raw files, in Portuguese, with different columns, formats, update dates and quirks. This Actor gives you:
- Rio de Janeiro, São Paulo and Belo Horizonte in one English schema with one vocabulary (
property_use,transaction_type,value_flag), instead of three different spreadsheets. - Quality flags you would otherwise build yourself:
value_flag(symbolic or multi-unit values),date_precision,geo_precision, partial transfers excluded from price per m², andnoteon statistics for source gaps and incomplete recent months. - Statistics and an index already computed, a stable
transaction_id, and queryable filters (date, district, neighbourhood, postcode prefix, use, value) through an API. - Source file and checksum on every row (
source_url,source_file_sha256).
Why use it?
- Three cities, one schema, one vocabulary (
property_use,transaction_type,value_flag), instead of three different spreadsheets with changing layouts. - Declared values from the tax authority (a different, earlier signal than asking prices), with price per m² where the data allows.
- Generalised location: district (IBGE administrative subdivision, a few per city), neighbourhood (bairro), the first 5 digits of the postcode (CEP) and a centroid whose precision is stated in
geo_precision. Never a property coordinate. - Run it from the Apify API, schedule it, or call it as an MCP tool from an AI agent.
Coverage
| City | Transactions | Period | Date precision | Transaction type | Tax base | Postcode | Neighbourhood |
|---|---|---|---|---|---|---|---|
| São Paulo | ~2.73 million (2006 to Sep 2026), plus 7,952 late-filed rows from 1994 to 2005 | 2006 to Sep 2026 | day | yes | yes | 5 digits | no (district only) |
| Rio de Janeiro | ~614 thousand | Jul 2011 to Aug 2026 | month only | no | no | no | yes |
| Belo Horizonte | ~514 thousand | 2008 to Aug 2026 | day (tax payment) | no | yes | 5 digits | yes |
The data is refreshed monthly. Each item carries source_url and source_file_sha256. The 1994–2005 São Paulo rows (7,952) are late filings in the source, not a complete series: use 2006 onwards for any time-series work.
Price per m² is not like-for-like across cities. Only São Paulo records the transaction nature, so only São Paulo statistics filter to sales (transaction_type = 'sale'). In Rio and Belo Horizonte the price per m² and the area_stats percentiles mix every nature of transfer (sales, gifts, swaps and others cannot be separated). Compare levels across cities with that in mind.
What data can it return?
Three datasets (input field dataset):
| Dataset | One row is | Main fields |
|---|---|---|
transactions | one ITBI transaction | transaction_date, city, transaction_type, value_declared_brl, value_tax_base_brl, built_area_m2, price_per_m2_brl, value_declared_usd, value_declared_eur, price_per_m2_usd, price_per_m2_eur, value_declared_brl_real, price_per_m2_brl_real, property_use, property_type, building_age_band, is_new_build, neighbourhood, district_code, postcode5, geo_precision, licence |
area_stats | city / district / neighbourhood × use × property type × month | property_type, n_transactions, n_priced, price_per_m2_p25, price_per_m2_median, price_per_m2_p75, the same percentiles in USD and EUR (price_per_m2_usd_median, price_per_m2_eur_p25, …), price_per_m2_brl_real_median, n_transactions_yoy_pct, note |
repeat_sales_index | segment × quarter (São Paulo) | index_value (2019-Q4 = 100), std_error, n_pairs_start, n_pairs_end, status: "experimental" |
Field-by-field definitions (types, per-city sources, where each is null) are in the data dictionary. area_stats and repeat_sales_index items also carry licence.
How to use it
- Choose a dataset and one or more cities (required).
- Add filters: dates, district, neighbourhood, postcode prefix, property use, transaction type, value range.
- Set Maximum rows (default 1,000, ceiling 100,000). Transactions are charged per row, so this is also your cost cap.
- Run it and download the dataset as JSON, CSV or Excel, or read it through the API.
Input examples
Each example below was run against the current data. Row counts and the cost (US$ 10 per 1,000 transactions rows, US$ 4 per 1,000 area_stats / repeat_sales_index rows) are noted; the Pricing tab is authoritative.
São Paulo residential sales in one district (Pinheiros), 2025 (1,000 rows returned, capped by max_rows; about US$ 10):
{ "dataset": "transactions", "city": ["sao_paulo"], "date_from": "2025-01-01", "date_to": "2025-12-31","district_code": "355030862", "property_use": ["residential"], "transaction_type": ["sale"], "max_rows": 1000 }
Monthly statistics for every Belo Horizonte neighbourhood, 2024–2025 (4,962 rows, about US$ 20):
{ "dataset": "area_stats", "city": ["belo_horizonte"], "area_level": ["neighbourhood"], "property_use": ["residential"],"date_from": "2024-01-01", "date_to": "2025-12-31", "max_rows": 100000 }
Apartments only, in one Rio neighbourhood (Leblon), September 2025 to August 2026 (12 rows, about US$ 0.05). property_type takes apartment or house in area_stats; without it you get only the all rows, as before:
{ "dataset": "area_stats", "city": ["rio_de_janeiro"], "neighbourhood": "LEBLON", "area_level": ["neighbourhood"], "property_type": ["apartment"],"date_from": "2025-09-01", "date_to": "2026-08-31", "max_rows": 24 }
In transactions, property_type is one of apartment, house, serviced_apartment, room_office, shop, warehouse, land, parking or other, and building_age_band (new, 3_10, 11_25, 26_plus) and is_new_build give the building age in São Paulo and Belo Horizonte (null in Rio, which has no construction year).
The experimental São Paulo index (85 rows, about US$ 0.34):
{ "dataset": "repeat_sales_index", "city": ["sao_paulo"], "segment": ["residential_all"] }
Finding district codes and neighbourhood names
Filters take an IBGE district code (district_code, São Paulo and Belo Horizonte) or a neighbourhood name (neighbourhood, Rio and Belo Horizonte), not free text. To list the valid ones, run a cheap area_stats query for one city, one level and one month, with property_use = ["all"], and read area_code and area_name from each row:
{ "dataset": "area_stats", "city": ["rio_de_janeiro"], "area_level": ["neighbourhood"], "property_use": ["all"],"date_from": "2026-06-01", "date_to": "2026-06-30", "max_rows": 300 }
This returns 146 rows (about US$ 0.58); São Paulo with "area_level": ["district"] returns 95 rows (area_code 355030862 is Pinheiros) and Belo Horizonte with ["neighbourhood"] returns 219. Names are uppercase without accents in Rio (LEBLON, COPACABANA); the neighbourhood filter ignores case and accents. Use a recent full month so that most areas appear; an area with no transaction in that month does not appear in it.
Output example (transactions)
{ "transaction_id": "3f2a…e9f", "city": "sao_paulo", "transaction_date": "2025-03-14", "date_precision": "day", "year": 2025, "year_month": "2025-03","transaction_type": "sale", "value_declared_brl": 450000.0, "value_tax_base_brl": 470000.0, "built_area_m2": 72.5, "price_per_m2_brl": 6206.9, "value_declared_usd": 82000.0, "value_declared_eur": 74500.0, "price_per_m2_usd": 1130.0, "price_per_m2_eur": 1027.0,"value_declared_brl_real": 480000.0, "price_per_m2_brl_real": 6620.0,"property_use": "residential", "neighbourhood": null, "district_name": "Example District", "postcode5": "05422", "geo_precision": "block_centroid","licence": "not declared" }
Use with AI agents (MCP)
The Apify MCP server lets an AI agent find this Actor in the Store, read its input schema and call it as a tool. The agent discovers it with search-actors and fetch-actor-details, runs it with call-actor (the run's maxTotalChargeUsd goes in callOptions, not in the Actor input) and reads the rows with get-dataset-items. Running Actors needs an Apify account; searching and reading details does not.
Connect. The hosted server is https://mcp.apify.com (sign-in through the browser, OAuth). To give the agent this Actor as its own tool, add it to the tools parameter:
https://mcp.apify.com?tools=pracadata/brazil-itbi-transactions
- Claude (Desktop or web): Settings → Connectors → Add custom connector, paste the URL above, sign in to Apify.
- Cursor, VS Code and other clients with an
mcpServersfile:
{"mcpServers": {"apify": { "url": "https://mcp.apify.com?tools=pracadata/brazil-itbi-transactions" }}}
Without a browser (servers, CI), use a token instead: add "headers": { "Authorization": "Bearer <APIFY_TOKEN>" } next to url. The Apify docs also describe a configurator at https://mcp.apify.com and the CLI command apify mcp install <client>.
What an agent should know. Statistics questions go to area_stats (cheap, one row per area and month); transactions is for the individual rows and costs 2.5 times more per row. Filters need a district code or a neighbourhood name from the discovery query above. Always set max_rows (it caps the cost) and a small maximum charge per run. The data has no address or property lookup, by design.
Questions, inputs and cost. Row counts come from runs against the current data; costs are the prices above.
| Question | Input (dataset; main fields) | Rows | Cost | Suggested max charge |
|---|---|---|---|---|
| "Median price per m² in Leblon over the last 12 months, in USD" | area_stats; city rio_de_janeiro, neighbourhood LEBLON, area_level neighbourhood, property_use residential, date_from 2025-09-01, date_to 2026-08-31. Read price_per_m2_usd_median | 12 | US$ 0.05 | US$ 0.25 |
| "How many residential transactions in Pinheiros in 2025?" | area_stats; city sao_paulo, district_code 355030862, area_level district, property_use residential, 2025. Sum n_transactions (all natures, apartments and houses together: 2,469) | 12 | US$ 0.05 | US$ 0.25 |
| "How many of those were sales, and at what values?" | transactions; same filters plus transaction_type sale (São Paulo only). Rows returned = number of sales (2,065); set max_rows high enough | 2,065 | US$ 20.65 | US$ 25 (or max_rows 100 to sample: US$ 1) |
| "Compare São Paulo, Rio and Belo Horizonte price per m² by month since 2020" | area_stats; city all three, area_level city, property_use residential, date_from 2020-01-01. Read price_per_m2_usd_median (cities differ in transaction mix: see Coverage) | 242 | US$ 0.97 | US$ 2 |
| "Which Rio neighbourhoods exist, and which names can I filter on?" | the discovery query above | 146 | US$ 0.58 | US$ 1 |
| "Rank Belo Horizonte neighbourhoods by median price per m² in July 2026" | area_stats; city belo_horizonte, area_level neighbourhood, property_use residential, date_from 2026-07-01, date_to 2026-07-31. Sort by price_per_m2_usd_median (null where fewer than 5 priced transactions) | 221 | US$ 0.88 | US$ 2 |
| "Median price per m² of apartments (not houses) in Leblon over the last 12 months, in USD" | area_stats; city rio_de_janeiro, neighbourhood LEBLON, area_level neighbourhood, property_type apartment, date_from 2025-09-01, date_to 2026-08-31. Read price_per_m2_usd_median (rows have property_use all: do not add property_use) | 12 | US$ 0.05 | US$ 0.25 |
| "Show 2025 residential transactions above R$ 2 million in Ipanema" | transactions; city rio_de_janeiro, neighbourhood IPANEMA, property_use residential, 2025, value_min 2000000, max_rows 100 (the cap was reached: raise it for the full list) | 100 | US$ 1.00 | US$ 2 |
| "Has São Paulo residential real estate beaten inflation since 2020?" | area_stats; city sao_paulo, area_level city, property_use residential, date_from 2020-01-01. Read price_per_m2_brl_real_median (constant reais, base month in RUN_SUMMARY.ipca_base_month) | 81 | US$ 0.32 | US$ 1 |
The first question as the exact input an agent sends (12 rows, run and checked):
{ "dataset": "area_stats", "city": ["rio_de_janeiro"], "neighbourhood": "LEBLON", "area_level": ["neighbourhood"], "property_use": ["residential"],"date_from": "2025-09-01", "date_to": "2026-08-31", "max_rows": 24 }
Cheap way to test a query: the Console form is prefilled with São Paulo and max_rows 10 (US$ 0.10 for transactions). A currency conversion uses the average rate of the month the tax was paid, not the deal date; see Known limitations.
Pricing
This Actor uses pay per event. Prices (live since 14 Oct 2026; the Pricing tab is authoritative):
| Event | What you are charged for | Price |
|---|---|---|
transactions-row | each transaction row returned by dataset = transactions | US$ 10 per 1,000 rows |
stats-row | each row returned by dataset = area_stats or repeat_sales_index | US$ 4 per 1,000 rows |
snapshot-download | the whole transaction table of one city as a Parquet file (paying Apify users only), 1 event per million rows started | US$ 500 per event: São Paulo 3 events (US$ 1,500), Rio de Janeiro 1 event (US$ 500), Belo Horizonte 1 event (US$ 500) |
The Pricing tab shows what you are charged.
Snapshots and your maximum charge per run. The snapshot is charged in full before the file is delivered, so the maximum charge per run you set when starting the Actor has to cover the whole amount: US$ 1,500 for São Paulo, US$ 500 for Rio de Janeiro or Belo Horizonte. If it does not, the run fails before anything is charged, the file is not delivered, and the error message states how many events and how many dollars the city needs; raise the limit and run again. Row queries (transactions, area_stats, repeat_sales_index) stop returning rows when your maximum-charge limit is reached, and RUN_SUMMARY says so (stopped_by_spending_limit).
What was removed, and why (privacy)
The sources contain the full address, and some also a tax-registry id (in São Paulo called SQL, Setor-Quadra-Lote; unrelated to the database language) or a property-registry number (matrícula). A property, and through the registry its owner, can be identified from them: personal data under Brazil's data-protection law (LGPD). The output therefore never contains street, address, street number, unit, tax-registry id, registry number, names, plot or block codes, plot area, frontage, or any plot-level coordinate. Locations are generalised centroids: a published point must represent at least 2 addresses and must not coincide with one, otherwise the row moves to a coarser level. transaction_id is a salted hash, not reversible.
Limit of this protection. Removing the identifiers does not make a row unlinkable from the public record it came from. source_url points to the municipality's file, which has the full address, and date + declared value + area can match a row in it. The product promises what it removes (no address, no property or tax id, no plot-level coordinate in the data returned), not that nobody can re-identify a transaction by going to the public source.
Licence and attribution
- Belo Horizonte: CC-BY. Items carry
licence = "CC-BY". If you publish or redistribute them you must attribute: Fonte: Prefeitura de Belo Horizonte, dados abertos. - São Paulo and Rio de Janeiro: the publisher declares no licence (
licence = "not declared"). The data is public and published by the municipalities, but no reuse terms are stated, and a licence request under Brazil's access-to-information law (LAI, Lei de Acesso à Informação) is pending. Check with your own counsel before commercial redistribution. - Exchange rates and inflation: Exchange rates: ECB euro reference rates (USD/BRL derived cross rate). Inflation: IBGE IPCA.
- Districts use the 2022 census geography of IBGE (Brazil's statistics institute); Rio's street-level locations derive from the Data.Rio street grid (CC-BY-4.0; credit "Fonte: Prefeitura do Rio de Janeiro, Data.Rio").
Terms
- Use: internal use (your own analysis, models, reports and products built on top of the data). Do not resell or redistribute the raw data, in volume or as a substantially similar dataset, snapshot or API.
- Exchange rates and inflation: Exchange rates: ECB euro reference rates (USD/BRL derived cross rate). Inflation: IBGE IPCA.
- Attribution by city: Belo Horizonte is CC-BY, so credit "Fonte: Prefeitura de Belo Horizonte, dados abertos" where you publish it. Rio's street-level locations derive from the Data.Rio street grid (CC-BY-4.0; credit "Fonte: Prefeitura do Rio de Janeiro, Data.Rio"). São Paulo and Rio transactions have no licence declared by the publisher (
licence = "not declared"): see Licence and attribution. - No warranty: the data is provided as is, from public sources, with the known limitations listed above. It is not an appraisal and not official.
- Cities can be withdrawn: if a municipality restricts the reuse of its data, the city may be removed from the product.
- Data subjects: removal requests and LGPD questions go to
lucashpissaia@gmail.com.
Known limitations
- Rio has month precision only, no transaction type, no tax base and no postcode. Only about a third of Rio rows are placed on a street segment; most are placed on the whole street or the neighbourhood.
- Belo Horizonte has a gap between June 2014 and April 2015 (missing source data, not a market drop) and a delayed load in May–August 2015 (peak of 10,150 rows in August 2015); both are flagged in
area_stats.note(bh_source_gap,bh_source_backlog). Belo Horizonte has no transaction type. - Late filings revise past months: the latest two to three months of every city are incomplete and flagged
recent_incompleteinarea_stats; counts grow in later releases. geo_precisionvaries by city and is not comparable across cities (a Rio street centroid is a whole street; a São Paulo block centroid is a city block; in Belo Horizonte 74% of rows sit on a block face, 20% on a postcode area, 6% on a neighbourhood).- Currencies.
*_usdand*_euruse the monthly average ECB rate of the month ofyear_month(USD/BRL is a derived cross rate); they are null before 2008-01 (the ECB has no BRL rate), andarea_statsmarks those monthsfx_not_available.*_brl_realis deflated by the IBGE IPCA to the month inRUN_SUMMARY.ipca_base_month(the latest published at build time), so it changes between releases. The ITBI date is not the deal date: conversions are indicative. The rates and the index are never returned. - ID change in Rio (one-off). On 14 October 2026, 47,322 Rio de Janeiro transaction_id values from 2025 onwards changed once because the city republished the file with normalised street names; IDs from 2011–2024 are unchanged.
- Partial transfers (about a quarter of São Paulo rows) have no
price_per_m2_brl: the declared value is for the share, the area for the whole property. São Paulo and Rio rows whose share is unknown also have no price per m². Belo Horizonte has no share column, so its prices cannot be checked for partial transfers. - The repeat-sales index is experimental, São Paulo only (the method needs a stable property id and only São Paulo's source has one), and is not validated against an external index.
- Values are nominal BRL, and are declared values, not appraisals. A neighbourhood label can be null (13 Belo Horizonte source rows carried a spreadsheet error and were set to null).
- Property type and building age come from the source codes.
property_typemaps the source's use or typology code (São Paulo: IPTU use code; Belo Horizonte: construction type; Rio: typology) with a published table; codes that are not classified (for example industrial, school, hotel, and a few ambiguous Belo Horizonte codes) areother.building_age_bandis the transaction year minus the construction year, only in São Paulo and Belo Horizonte; a sale of a building not yet finished (construction year after the transaction year, up to 30 years ahead: off-plan sales) counts asnew. It is null for land and when the source has no plausible year (before 1800, or more than 30 years ahead, which are typing errors). The exact construction year is not published.is_new_buildis true for 1 year or less, off-plan sales included. - Some free-text fields keep the source's Portuguese:
neighbourhood,district_name,property_use_source.
Not supported: address or property lookup
This Actor will refuse any input that tries to find a specific property or person: street or address, street number, tax id (SQL), registry number (matrícula), full 8-digit postcode, coordinates or radius. This is a product rule, not a missing feature. Use the district, neighbourhood or 5-digit postcode prefix instead.
FAQ and support
Is personal data included? The rows carry no names, tax ids, registry numbers or addresses. But they are not anonymous: every item has a source_url that points to the municipality's public file, and that file does contain the full address; the combination of transaction date, declared value and area can also be matched against it. We removed the identifiers, but we cannot make a public record unlinkable from itself. Treat the data as public records about property transactions, not as anonymised data, and do not use it to identify, locate or contact individuals. Data subjects can ask for the removal of a row or raise a data-protection (LGPD) question at lucashpissaia@gmail.com.
Can I get everything for a city? Paying users can use "Download a whole city as Parquet" (one city, no filters) instead of paging rows.
Can I use it from an AI agent? Yes. Pay-per-event Actors are available as MCP (Model Context Protocol) tools on Apify; the input fields above are the tool arguments. See "Use with AI agents (MCP)" for the connection URL, example questions and suggested spending limits.
Found a problem or need another field? Use the Issues tab.