# ZAP Imoveis Scraper: Brazil Property Listing Alerts (`accountable_eel/zapimoveis-listing-lookup`) Actor

ZAP Imoveis scraper by search: get one row per listing (price, condo fee, IPTU, area, bedrooms, bathrooms, parking, neighbourhood, link). Turn on monitoring for only new listings and price drops since your last run. No login required; empty searches and quiet monitoring runs are free.

- **URL**: https://apify.com/accountable\_eel/zapimoveis-listing-lookup.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.52 / 1,000 listing returneds

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/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

## ZAP Imoveis Scraper: Brazil Property Listing Alerts

Watch a property search, get only what's new. This actor runs your search against **ZAP
Imoveis' own search-results pages** — one of Brazil's two largest real-estate marketplaces — and
returns one clean row per listing: price, condo fee, IPTU, area, bedrooms, bathrooms, parking,
neighbourhood, and a permanent link. Turn on monitoring and it becomes a watchlist: new listings
and price drops since your last run, charged per new row, free on quiet days. Schedule it hourly
and send the results to Discord, Slack, Google Sheets, or n8n.

### Who it's for

A renter or buyer watching one neighbourhood or city for newly-posted apartments wants a live feed
of what's actually on ZAP Imoveis right now, instead of refreshing the search page by hand — ZAP
has no saved-search alert of its own. An investor tracking a market for underpriced or recently
discounted listings, a relocation agency monitoring several cities for clients at once, or a
Sheets/n8n workflow that reacts the moment something new is posted, gets the same shape here: paste
a location or a search URL, get back a flat row per listing (including condo fee and IPTU, which
most scrapers skip), and pay only for listings actually returned — a search that finds nothing costs
nothing.

**ZAP Imoveis and VivaReal share inventory and a codebase.** The same property is often listed on
both sites (Grupo OLX Brasil owns both). This actor's sibling, `vivareal-listing-lookup`, runs the
identical search and monitoring shape against VivaReal — dedupe across the two by `listingId`,
which is the same numeric ZAP/VivaReal property id on both. OLX Brasil's general classifieds site is
a different front end entirely and is covered by a separate actor, not this family.

### Why this one

- **Monitoring first, not a one-off dump.** Turn on "Only return results that are new, or dropped
  in price" and this actor becomes a watchlist: it remembers every listing ID it has already shown
  you, and only bills you for what's actually new or has dropped in price since the last run. A
  quiet run — nothing new, nothing cheaper — still finishes and costs only the base run fee.
- **Reads the condo fee and property tax, not just the headline price.** Every card on ZAP's own
  search pages carries "Cond. R$ X • IPTU R$ Y" next to the price — this actor parses both into
  their own `condoFee` and `iptu` columns (0 when the listing marks them "isento"/exempt, empty
  when the site doesn't show the line at all), which most listing scrapers skip entirely.
- **A real posted date, not a guess.** ZAP's page ships a second, easy-to-miss JSON-LD block
  (`RealEstateListing.mainEntity`) that carries each listing's actual `datePosted` — this actor
  reads it directly rather than fabricating one from "time ago" text.
- **Never charged for a miss, or a quiet monitoring run.** A search that finds nothing, or a
  scheduled watchlist run where nothing changed, still gets a row explaining what happened — and
  costs nothing.
- **Paste a location or your own saved search URL.** Type a state+city (optionally +neighbourhood),
  or paste a ZAP Imoveis search-results URL you've already filtered on the site — its filters carry
  straight through.

### What you get

One row per listing by default (turn off "Expand rows" in Advanced to get one row per search
instead, with the listings nested under `listings`).

| Field | Type | Description |
|---|---|---|
| `query` | text | The search you submitted, echoed back |
| `found` | boolean | `true` if ZAP Imoveis returned a real search-results page for this search |
| `status` | text | `OK` on a match; `NOT_FOUND`, `BLOCKED`, or `BAD_FORMAT` on a miss |
| `message` | text | Plain-English reason for a miss — only present when `found` is `false` |
| `searchQuery` | text | The search line you submitted (location, or the pasted URL) |
| `listingCount` | number | Listings returned and billed for this search |
| `truncated` | boolean | `true` if a page couldn't be fetched (even after one retry) before the requested count was reached |
| `newCount` | number | New listings this run (monitoring only) |
| `priceDropCount` | number | Price drops this run (monitoring only) |
| `monitorStatus` | text | `NO_NEW_ROWS` on a quiet monitoring run, `WATCHLIST_SEEDED` on a silent first run, otherwise empty |
| `listingId` | text | ZAP Imoveis' own numeric listing ID (shared with VivaReal for the same property) |
| `title` | text | Listing title |
| `operation` | text | `buy` or `rent` |
| `price` | number | Price in BRL |
| `currency` | text | Always `BRL` |
| `condoFee` | number | Monthly condo fee in BRL; `0` when the listing marks it "isento" (exempt) |
| `iptu` | number | Annual property tax (IPTU) in BRL; `0` when marked "isento" |
| `areaM2` | number | Floor area in square metres |
| `bedrooms` | number | Bedroom count |
| `bathrooms` | number | Bathroom count |
| `parkingSpaces` | number | Parking spaces |
| `floorLevel` | text | Floor level, when ZAP's listing data has one |
| `street` | text | Street address |
| `neighbourhood` | text | Neighbourhood (bairro) |
| `city` | text | City |
| `state` | text | State (two-letter UF code) |
| `adTier` | text | ZAP's own ad tier for this listing (e.g. "SUPER PREMIUM"), when it has one |
| `advertiserName` | text | The advertiser's name, **only** when it is clearly a business/agency — a private seller's name is never output (see "Data & privacy") |
| `postedAt` | date | The listing's real posted date, from ZAP's own data |
| `photoCount` | number | Number of photos ZAP's listing data lists |
| `imageUrl` | text (link) | First listing image |
| `description` | text | Listing description, with any email/phone/WhatsApp-link-like text redacted |
| `changeType` | text | `new`, `price-drop`, or `seen` (monitoring only) |
| `isNew` | boolean | `true` on this listing's first appearance (monitoring only) |
| `previousPrice` | number | The price last recorded for this listing, when it dropped (monitoring only) |
| `priceDropPct` | number | Percentage drop from the previous recorded price (monitoring only) |
| `firstSeenAt` | date | When this watchlist first saw this listing (monitoring only) |
| `url` | text (link) | Permanent listing link |
| `scrapedAt` | date | When this row was fetched |

Deselect any field in the Input tab's column picker to drop it from every row — `query`, `found`,
`status`, `message`, and `scrapedAt` always stay. This actor does not declare a `sellerHash` or
`totalAvailable` column: ZAP's search pages don't expose an advertiser ID (so a hash can't be
computed) or a reliable total-match count on this route — see the FAQ.

### Pricing

Pay-per-event. A flat per-run fee covers session/proxy warmup; you're billed
per item only when data is actually found and returned — see
`.actor/pay_per_event.json` for exact prices. A miss is never charged.

### Monitoring / only new results

Turn on **"Only return results that are new, or dropped in price"** and a search line becomes a
watchlist instead of a one-off dump:

- A listing counts as **new** the first time its ZAP Imoveis listing ID is returned by this
  watchlist.
- A listing counts as a **price drop** when its price falls by at least "Minimum price drop" (5%
  by default) since it was last seen — measured against the most recently seen price, so a listing
  that drops twice is compared against its latest price each time, not its original one.
- Already-seen, unchanged listings are dropped before you're billed. A quiet run — nothing new,
  nothing cheaper — still finishes and returns one free row with `monitorStatus: NO_NEW_ROWS`, so a
  scheduled task shows it actually ran.
- The **first run has nothing to compare against.** By default it returns everything it finds as
  "new" (so you see your whole current search result once). Turn on **"Seed silently"** to instead
  have the first run bank everything quietly with zero rows and zero charge, and start alerting
  from the second run on — this is what stops a fresh watchlist from firing a hundred "new listing"
  messages into your Discord channel on day one.
- Results are sorted by each listing's own posted date whenever monitoring is on (ZAP's own search
  pages carry no sort parameter to force this server-side), so two runs of the same watchlist are
  always comparing the same ordering.

**Schedule recipe.** Save this actor as an Apify **Task** with `deltaMode: true` and a
`deltaName` you choose, add an hourly (or daily) **Schedule**, and point the task's **webhook** at
Discord, Slack, Google Sheets, or an n8n/Make webhook URL to get pinged only when something
actually changes.

**Price-drop window limitation.** A price drop is only ever seen while the listing is still inside
what a run actually fetches — if a listing falls out of your `maxListingsPerSearch` window or your
price filters between runs, this actor has no way to notice it dropped. Keep searches reasonably
narrow (one city, one neighbourhood, a tight price band) rather than one huge catch-all if
price-drop coverage matters to you.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `searches` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~zapimoveis-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"searches":["sp+sao-paulo"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

4) Turn on "Only return results that are new, or dropped in price" under Monitoring once you want
   a watchlist instead of a one-off dump — see "Monitoring / only new results" above.

### Input

```json
{
  "searches": [
    "sp+sao-paulo"
  ]
}
```

One per line: paste a full zapimoveis.com.br search-results URL (its own filters are kept), or type a location as state+city, e.g. "sp+sao-paulo" or "sp+sao-paulo+pinheiros" for a neighbourhood — combined with "Operation" and "Property type" below. No login required. Accepted formats: sp+sao-paulo, rj+rio-de-janeiro, https://www.zapimoveis.com.br/venda/apartamentos/sp+sao-paulo/.

`operation` (buy/rent) and `propertyType` (apartment, house, house in a condominium, land, or
commercial) apply to any search line that's a location rather than a pasted URL — a pasted URL
keeps whatever operation and property type it already has.

### Sample output

| query | found | status | searchQuery | listingCount | totalAvailable | truncated | newCount | priceDropCount | monitorStatus | listings | listingId | title | operation | price | currency | condoFee | iptu | areaM2 | bedrooms | bathrooms | parkingSpaces | floorLevel | street | neighbourhood | city | state | adTier | advertiserName | postedAt | photoCount | imageUrl | description | changeType | isNew | previousPrice | priceDropPct | firstSeenAt | url | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| sp+sao-paulo | true | OK | <search> | <listings returned> | <total matching on zap imoveis> | <more results were available> | <new listings this run> | <price drops this run> | \<monitoring status (quiet / seeded runs)> | \<all listings found (full list)> | <zap imoveis listing id> | <title> | <buy or rent> | \<price (brl)> | <currency> | \<condo fee (brl/month)> | \<iptu property tax (brl/year)> | \<area (m²)> | <bedrooms> | <bathrooms> | <parking spaces> | <floor level> | <street> | <neighbourhood> | <city> | \<state (uf)> | \<ad tier (standard / premium / super premium)> | \<advertiser (agencies only)> | <posted> | <photo count> | <image> | <description> | \<new / price-drop / seen> | \<is this listing new?> | \<previous price (brl)> | \<price drop %> | <first seen on a run> | <listing link> | 1970-01-01T00:00:00.000Z |

A search that finds nothing comes back as a row with `"found": false` and is never charged. A quiet
monitoring run comes back as a row with `"found": true, "monitorStatus": "NO_NEW_ROWS"` and is also
never charged.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~zapimoveis-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"searches":["sp+sao-paulo"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~zapimoveis-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"searches":["sp+sao-paulo"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~zapimoveis-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"searches":["{{search}}"]}`, mapping the row's search into the `searches` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "ZAP Imoveis Scraper | Apify" — the agent will find and run this actor.

For monitoring, point the Task's webhook (Console → your Task → Webhooks) at a Discord or Slack
incoming-webhook URL, a Google Sheets automation (via Zapier/Make/n8n listening for the webhook),
or an n8n/Make workflow's own webhook trigger, so new listings and price drops land wherever you
actually work instead of a dataset you have to remember to check.

### Tips

- Start monitoring with "Seed silently" on for any watchlist you're setting up on a schedule — it
  avoids a wall of "new" alerts for your entire current search result on day one.
- Keep a monitored search reasonably narrow (one city or neighbourhood, a price band) — see
  "Price-drop window limitation" above for why a huge catch-all search misses price drops on
  listings that fall outside what gets fetched.
- Dedupe against `vivareal-listing-lookup` on `listingId` — ZAP and VivaReal share inventory, so
  the same apartment can legitimately appear on both.
- A `BLOCKED` status means ZAP Imoveis returned a 403/429/503, or an UNBLOCKER 502, even after this
  actor's own one retry on a fresh session — trying again shortly usually clears it.
- Paste your own already-filtered ZAP Imoveis search URL instead of rebuilding filters in this
  actor's Input tab — anything the URL already filters on carries straight through.

### vs. fatihtahta/zap-imoveis

The current Apify Store leader for ZAP Imoveis, `fatihtahta/zap-imoveis`, has 57 users in the last
30 days (394 lifetime) and 5 stars, but no monitoring mode. This actor adds new-listing and
price-drop monitoring as a first-class mode, reads the condo fee and IPTU that most scrapers skip,
and bills only for listings actually delivered.

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** | Pay per listing returned; nothing for a miss or a quiet monitoring run | Condo fee, IPTU, real posted date, monitoring mode (new + price drop) | An advertiser's name is output only when it's clearly an agency; no seller ID exists on this route to hash or dedupe by |
| **fatihtahta/zap-imoveis** | Per Store pricing | Established, well-rated | No monitoring: a one-off dump, run it yourself on a schedule if you want alerts |
| Doing it yourself | Your time to build and maintain a scraper, a seen-listing store, and a price-tracking layer | The same data | This actor's dual JSON-LD parsing, monitoring state, and redaction are the maintenance burden it absorbs |

No dedicated actor covers cross-marketplace monitoring: the GitHub project `imovel-bot` scores
listings 0-100 across OLX, ZAP and QuintoAndar by sweeping them itself — this actor is the hosted,
schedulable version of the ZAP half of that job.

### Data & privacy

This actor reads public search results that anyone can see without logging in. It doesn't log in,
solve CAPTCHAs, or reveal hidden contact details. Seller identity is off by default: ZAP Imoveis
doesn't expose an advertiser ID anywhere in the pages this actor reads, so this actor only ever
outputs an advertiser's display name when it clearly reads as a business or agency — never a
private seller's name. Agent and owner phone numbers, WhatsApp numbers and emails are never
collected, and any that appear inside a listing's description are redacted to `[redacted]`. Not
affiliated with ZAP Imoveis / Grupo OLX Brasil.

**LGPD.** Brazil's Lei Geral de Proteção de Dados is GDPR-like. Seller info is off by default here,
and — unlike a toggle a buyer could turn on — a private seller's name is never output by this actor
at all, on this route.

**Is this allowed?** It collects the same public listing data your browser shows, for the searches
you choose. It's built for monitoring a search, not for copying the marketplace. Check that your
use fits ZAP Imoveis' terms and your local law.

### FAQ

**Why is a row empty, or why does `found` say `false`?**
Either the input line was blank (`status: BAD_FORMAT`), ZAP Imoveis returned a 403/429/503 or an
UNBLOCKER 502 even after one retry (`status: BLOCKED`), or the search matched nothing on ZAP right
now (`status: NOT_FOUND`). Check the `message` column for the specific reason. None of these are
billed.

**Am I charged for a quiet monitoring run?**
No. When monitoring finds nothing new and no price drops, you get one row with
`monitorStatus: NO_NEW_ROWS` and `listingCount: 0` — `Actor.charge()` only fires when
`listingCount` is greater than zero.

**Why isn't there a `sellerHash` or seller ID column?**
ZAP Imoveis' search-results pages don't expose an advertiser ID anywhere this actor can read — only
a display name, sometimes a private person's, sometimes an agency's. Rather than fabricate an ID or
hash a name (which would leak a private seller's identity through the hash's stability), this actor
outputs `advertiserName` only when the name is clearly a business, and nothing at all otherwise.

**Why isn't there a `totalAvailable` column?**
ZAP's search pages don't carry a reliable "N properties match this search" total on the static
route this actor reads (confirmed directly, not assumed) — so rather than show a stale or partial
number, this actor doesn't declare the column at all.

**What does `truncated: true` mean?**
This actor tried to fetch another page toward your `maxListingsPerSearch` limit and that page came
back blocked (or with an empty/unreadable body) even after one retry on a fresh UNBLOCKER session —
so you got fewer listings than requested. Try again shortly.

**Can I paste my own filtered ZAP Imoveis search instead of using this actor's price filters?**
Yes. Paste the full search-results URL from your browser's address bar as a search line — any
filters already in it (price range, bedrooms) carry through unchanged.

**Do I need to configure proxies?**
This actor requires Apify's UNBLOCKER proxy group by default — ZAP Imoveis is Cloudflare-protected
and blocks plain datacenter and residential requests alike. UNBLOCKER is already configured; you
don't need to change anything.

**Can an AI agent call this directly?**
Yes. It's registered on the Apify MCP server — an agent in Claude, Cursor, or another MCP client
can find and run it by name ("ZAP Imoveis Scraper | Apify"), or you can call the REST endpoint shown
above from any script or workflow tool.

### Related actors

- [VivaReal Scraper](https://apify.com/accountable_eel/vivareal-listing-lookup) — the same search
  and monitoring shape for VivaReal, which shares inventory and a codebase with ZAP Imoveis (both
  owned by Grupo OLX Brasil). Dedupe the two on `listingId`.

# Actor input Schema

## `searches` (type: `array`):

One per line: paste a full zapimoveis.com.br search-results URL (its own filters are kept), or type a location as state+city, e.g. "sp+sao-paulo" or "sp+sao-paulo+pinheiros" for a neighbourhood — combined with "Operation" and "Property type" below. No login required. Accepted formats: sp+sao-paulo, rj+rio-de-janeiro, https://www.zapimoveis.com.br/venda/apartamentos/sp+sao-paulo/. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Matched against the title, description, street and neighbourhood (values, not field names). Case-insensitive.

## `excludeKeywords` (type: `array`):

Optional. Same fields as above.

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

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `operation` (type: `string`):

Applied to any search line that is a location (state+city), not a pasted URL. A pasted URL keeps whatever operation it already has.

## `propertyType` (type: `string`):

Applied to any search line that is a location (state+city), not a pasted URL, using ZAP Imoveis' own path slugs.

## `sort` (type: `string`):

ZAP Imoveis' search results carry no sort parameter in the page itself (confirmed live 2026-09-18), so this actor sorts the fetched page(s) itself using each listing's own posted date or price. Forced to "Newest first" whenever monitoring is on.

## `maxListingsPerSearch` (type: `integer`):

ZAP Imoveis shows 30 listings per page; this actor pages through UNBLOCKER to reach your limit (one paid proxy request per page). You pay per listing returned, so this is also your budget control.

## `minPrice` (type: `integer`):

Optional. Drop listings priced below this. Leave empty for no minimum.

## `maxPrice` (type: `integer`):

Optional. Drop listings priced above this. Leave empty for no maximum.

## `minBedrooms` (type: `integer`):

Optional. Drop listings with fewer bedrooms than this.

## `deltaMode` (type: `boolean`):

Turns this actor into a monitor. A listing counts as new when its ZAP Imoveis listing ID has not been returned by a previous run of the same watchlist, and as a price drop when its price falls since it was last seen (see "Minimum price drop" below). Already-seen, unchanged listings are dropped before you are billed, so a quiet run costs only the run fee. The first run has nothing to compare against, so (unless "Seed silently" is on) it returns everything and remembers it.

## `deltaName` (type: `string`):

Leave empty and we derive one from this run's search settings, so two schedules with different settings keep separate memories. Type your own name to keep one memory across a settings change, or to have two schedules share one.

## `alertOnNew` (type: `boolean`):

On by default. Turn off to have this watchlist report price drops only, not brand-new listings.

## `alertOnPriceDrop` (type: `boolean`):

On by default. Turn off to have this watchlist report new listings only, ignoring price changes on ones you've already seen.

## `minPriceDropPct` (type: `integer`):

A listing must drop by at least this percentage since it was last seen to be reported as a price-drop.

## `skipFirstRun` (type: `boolean`):

When on, instead of returning every current listing as "new" the first time a watchlist runs, this banks them silently and starts alerting from the second run on. Stops a fresh watchlist from spamming a hundred messages to your webhook on day one.

## `includeSellerInfo` (type: `boolean`):

Off by default. ZAP Imoveis does not expose an advertiser ID on this route, so this has no effect yet — left in the schema so a saved task that sets it keeps working if this actor adds seller fields later. Agent and owner phone numbers, WhatsApp numbers and emails are never collected, on or off.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each listing found gets its own row instead of being grouped under its search. You're still only charged once per search, no matter how many rows it produces.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

## `proxyConfiguration` (type: `object`):

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "searches": [
    "sp+sao-paulo"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "operation": "buy",
  "propertyType": "apartment",
  "sort": "newest",
  "maxListingsPerSearch": 60,
  "deltaMode": false,
  "deltaName": "",
  "alertOnNew": true,
  "alertOnPriceDrop": true,
  "minPriceDropPct": 5,
  "skipFirstRun": false,
  "includeSellerInfo": false,
  "columns": [
    "searchQuery",
    "listingCount",
    "totalAvailable",
    "truncated",
    "newCount",
    "priceDropCount",
    "monitorStatus",
    "listings",
    "listingId",
    "title",
    "operation",
    "price",
    "currency",
    "condoFee",
    "iptu",
    "areaM2",
    "bedrooms",
    "bathrooms",
    "parkingSpaces",
    "floorLevel",
    "street",
    "neighbourhood",
    "city",
    "state",
    "adTier",
    "advertiserName",
    "postedAt",
    "photoCount",
    "imageUrl",
    "description",
    "changeType",
    "isNew",
    "previousPrice",
    "priceDropPct",
    "firstSeenAt",
    "url"
  ],
  "expandRows": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searches": [
        "sp+sao-paulo"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/zapimoveis-listing-lookup").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 = {
    "searches": ["sp+sao-paulo"],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/zapimoveis-listing-lookup").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 '{
  "searches": [
    "sp+sao-paulo"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/zapimoveis-listing-lookup --silent --output-dataset

```

## MCP server setup

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

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/XO14dmvnqv0RkagH2/builds/mT4H8dKcBIPWn4MX9/openapi.json
