# Brazil Real Estate Transactions & Prices: São Paulo, Rio, BH (`pracadata/brazil-itbi-transactions`) Actor

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.

- **URL**: https://apify.com/pracadata/brazil-itbi-transactions.md
- **Developed by:** [Lucas Huber Pissaia](https://apify.com/pracadata) (community)
- **Stats:** 3 total users, 2 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 transaction rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Brazil Real Estate Transactions & Prices: São Paulo, Rio, BH

### 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](https://huggingface.co/datasets/pracadata/brazil-itbi-belo-horizonte/blob/main/DATA_DICTIONARY.md).

**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², and `note` on 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](https://huggingface.co/datasets/pracadata/brazil-itbi-belo-horizonte/blob/main/DATA_DICTIONARY.md). `area_stats` and `repeat_sales_index` items also carry `licence`.

### How to use it

1. Choose a **dataset** and one or more **cities** (required).
2. Add filters: dates, district, neighbourhood, postcode prefix, property use, transaction type, value range.
3. Set **Maximum rows** (default 1,000, ceiling 100,000). Transactions are charged per row, so this is also your cost cap.
4. 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):

```json
{ "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):

```json
{ "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:

```json
{ "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):

```json
{ "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:

```json
{ "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)

```json
{ "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 `mcpServers` file:**

```json
{
  "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):

```json
{ "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_incomplete` in `area_stats`; counts grow in later releases.
- **`geo_precision` varies 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.** `*_usd` and `*_eur` use the monthly average ECB rate of the month of `year_month` (USD/BRL is a derived cross rate); they are null before 2008-01 (the ECB has no BRL rate), and `area_stats` marks those months `fx_not_available`. `*_brl_real` is deflated by the IBGE IPCA to the month in `RUN_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_type` maps 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) are `other`. `building_age_band` is 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 as `new`. 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_build` is 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.

# Actor input Schema

## `dataset` (type: `string`):

What to return. `transactions`: one row per ITBI transaction (value, area, price per m², use, district, neighbourhood, postcode prefix); charged per row. `area_stats`: one row per area × use × month with transaction counts and price per m² percentiles (BRL, USD, EUR, inflation-adjusted BRL); charged per row, cheaper than transactions; the right choice for questions about medians, trends or comparisons. `repeat_sales_index`: quarterly São Paulo price index, experimental (São Paulo only). Default: transactions.

## `city` (type: `array`):

Required. One or more cities: `sao_paulo`, `rio_de_janeiro`, `belo_horizonte`. Coverage: São Paulo 2006 to Sep 2026 (day precision), Rio de Janeiro 2011 to Aug 2026 (month precision only), Belo Horizonte 2008 to Aug 2026 (day precision). `repeat_sales_index` only has `sao_paulo`.

## `date_from` (type: `string`):

First transaction date, format YYYY-MM-DD (example 2025-01-01). `transactions`: compared to the transaction date; Rio dates are always the first day of the month, so use the first day of a month to include it. `area_stats`: only the year and month are used. `repeat_sales_index`: the quarter of the date. Optional; no value means from the start of the series.

## `date_to` (type: `string`):

Last transaction date, format YYYY-MM-DD (example 2025-12-31), inclusive. For `area_stats` only year and month are used. Optional; no value means up to the latest data. The latest two to three months are incomplete (see `note` = `recent_incomplete` in `area_stats`).

## `district_code` (type: `string`):

IBGE district code, digits only (example 355030862 = Pinheiros, São Paulo). `transactions` and `area_stats` (matches `area_code` at level `district`). Not a name: to look a code up, run `area_stats` for the city with `area_level` = \[`district`], a single month and `property_use` = \[`all`], and read `area_code` and `area_name`.

## `neighbourhood` (type: `string`):

Neighbourhood (bairro) name, Rio de Janeiro and Belo Horizonte only (São Paulo has districts, not neighbourhoods). Case and accents are ignored: `LEBLON`, `Leblon` and `leblon` are the same. Must match the name as published, for example `LEBLON` or `COPACABANA` (Rio); to list valid names run `area_stats` for the city with `area_level` = \[`neighbourhood`], one month and `property_use` = \[`all`], and read `area_name`. `transactions` and `area_stats`.

## `postcode5` (type: `string`):

First 5 digits of the Brazilian postcode (CEP), example 05422. Exactly 5 digits; full 8-digit postcodes are refused. `transactions` only. Rio de Janeiro rows have no postcode.

## `property_use` (type: `array`):

Filter by property use, one or more values. `transactions`: `residential`, `commercial`, `land`, `parking`, `other`. `area_stats`: `residential`, `commercial` or `all` (every use together, including land and parking; use `all` for a total). Apartments and houses are both `residential`. No value means every use (for `area_stats`, this returns one row per use, so pass \[`residential`] or \[`all`] to avoid triple rows).

## `property_type` (type: `array`):

Filter by property type, one or more values. `transactions`: `apartment`, `house`, `serviced_apartment`, `room_office`, `shop`, `warehouse`, `land`, `parking`, `other`; no value means every type (no filter). `area_stats`: only `all`, `apartment` or `house`; no value returns only the `all` rows (every type together), exactly as before this filter existed, so add \[`apartment`] to get apartment statistics at the same cost per row. Apartment and house rows in `area_stats` have `property_use` = `all`, so do not combine them with `property_use` = `residential`. Not available for `repeat_sales_index`. The type comes from the source (São Paulo: IPTU use code; Belo Horizonte: construction type; Rio: typology); codes the source uses that are not classified become `other`.

## `transaction_type` (type: `array`):

Filter by the nature of the transfer. São Paulo only: Rio and Belo Horizonte sources have no transaction nature, so rows from those cities have a null type and are excluded if you set this filter. Use \[`sale`] for market sales. `transactions` only.

## `value_min` (type: `number`):

Minimum declared transaction value in Brazilian reais (BRL), nominal, as declared on the tax guide (example 500000). `transactions` only.

## `value_max` (type: `number`):

Maximum declared transaction value in Brazilian reais (BRL), nominal (example 2000000). `transactions` only.

## `area_level` (type: `array`):

`area_stats` only: which area levels to return, one or more of `city`, `district`, `neighbourhood`. Empty returns all three levels, which triples the rows and the cost; São Paulo has `city` and `district`, Rio has `city` and `neighbourhood`, Belo Horizonte has all three.

## `segment` (type: `array`):

`repeat_sales_index` only (experimental, São Paulo): which property segments to return. Empty returns all three. `residential_all` = apartments and houses together.

## `max_rows` (type: `integer`):

Maximum number of rows returned (1 to 100,000; default 1,000). Rows are charged one by one, so this is also the cost cap: 1,000 transaction rows is US$ 10, 1,000 statistics rows US$ 4. Set a small value (for example 10) to test a query. If the filter matches more rows, the rest is not returned: `RUN_SUMMARY.capped_by_max_rows` is true.

## `download_snapshot` (type: `boolean`):

Paying Apify users only. Returns one city's entire transaction table as a Parquet file in the key-value store (event `snapshot-download`, US$ 500 per million rows started: São Paulo US$ 1,500, Rio de Janeiro or Belo Horizonte US$ 500). Requires dataset `transactions`, exactly one city and no other filter; the run's maximum charge must cover the whole amount. Leave off for normal queries.

## Actor input object example

```json
{
  "dataset": "area_stats",
  "city": [
    "rio_de_janeiro"
  ],
  "date_from": "2025-01-01",
  "date_to": "2025-12-31",
  "district_code": "355030862",
  "neighbourhood": "LEBLON",
  "postcode5": "05422",
  "property_use": [
    "residential"
  ],
  "property_type": [
    "apartment"
  ],
  "transaction_type": [
    "sale"
  ],
  "value_min": 500000,
  "value_max": 2000000,
  "area_level": [
    "neighbourhood"
  ],
  "segment": [
    "residential_all"
  ],
  "max_rows": 100,
  "download_snapshot": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with the returned rows. Which view applies depends on the input field `dataset`

## `files` (type: `string`):

Key-value store with RUN_SUMMARY and snapshot files

## `summary` (type: `string`):

RUN_SUMMARY: applied filters, rows returned, data build date, IPCA base month of the constant-BRL values and whether the maximum charge stopped the run

# 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 = {
    "city": [
        "sao_paulo"
    ],
    "max_rows": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("pracadata/brazil-itbi-transactions").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 = {
    "city": ["sao_paulo"],
    "max_rows": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("pracadata/brazil-itbi-transactions").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 '{
  "city": [
    "sao_paulo"
  ],
  "max_rows": 10
}' |
apify call pracadata/brazil-itbi-transactions --silent --output-dataset

```

## MCP server setup

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

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/83mGFETiwPus65pp9/builds/h7LVdTlATxUwumXfR/openapi.json
