# Trulia Scraper - Property Listings, Prices, Agents & History (`abotapi/trulia-scraper`) Actor

Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds, baths, square footage, addresses, GPS coordinates, photos, agents, and brokers, plus detailed price and tax history, property features, and open house information.

- **URL**: https://apify.com/abotapi/trulia-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 0 monthly users, 89.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 listing scrapeds

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

## Trulia Scraper: Property Listings, Prices, Photos & Agent Contacts

Trulia Scraper turns Trulia into a clean property data feed for the United States. Search any US location for homes for sale, for rent, or recently sold, or paste Trulia search and property links you already have. Every listing comes back as one record with price, beds, baths, square footage, full address, GPS coordinates, photo gallery, status tags, and the listing agent and broker. Turn on detail mode to also pull price history, tax history, the full feature list, open houses, agent contact, and affordability data straight from each property page, then export to JSON, CSV or Excel, or read it through the API.

### Why This Scraper?

- **Rich cards out of the box.** Search results already carry price, beds, baths, size, full address, neighborhood, GPS coordinates, photo gallery, status tags and the listing agent and broker name, with no extra fetch needed.
- **Optional deep detail.** One toggle adds price history, tax history, the full feature list, open houses, agent and broker contact, local protections, affordability data and estimated rent from the property page.
- **Three listing channels.** For sale, for rent, and recently sold, all from the same search fields.
- **Real keyword filter.** Narrow a location to listings whose text mentions a term such as pool, waterfront or garage.
- **Two ways to start.** Build a search from simple fields, or paste Trulia search and property links you already have, with automatic pagination on search links.
- **Predictable runs.** Detail mode is off by default so a standard run stays fast and low cost, and Max items keeps every run's size and spend capped.
- **Built for schedules.** Resume continues one interrupted crawl, and Incremental mode turns a recurring search into a feed of only what is new, updated or reappeared.

### Use Cases

- **Real estate investors and flippers:** track new listings and price drops for sale in target neighborhoods.
- **Rental market research:** collect current for-rent inventory and pricing across a metro area or ZIP code.
- **Market analysts and appraisers:** build historical comps from recently sold listings, including price and sale history.
- **PropTech and data teams:** feed a normalized property dataset into an internal tool, model or dashboard.
- **Relocation and lead-generation sites:** enrich listing pages with fresh price, photo and agent contact data.
- **Agents and brokers:** monitor a search for new listings or status changes and get only the changes on a schedule.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `id` | `"00000001"` |
| `url` | `"https://www.trulia.com/home/sample-address-ny-10000-00000001"` |
| `listingType` | `"FOR_SALE"` (also `"FOR_RENT"`, `"SOLD"`) |
| `propertyType` | `"SINGLE_FAMILY_HOME"` |
| `price` / `formattedPrice` | `750000` / `"$750,000"` |
| `streetAddress` | `"123 Sample Street"` |
| `city` / `state` / `zipCode` | `"Sampletown"` / `"NY"` / `"10000"` |
| `neighborhood` | `"Sample Heights"` |
| `latitude` / `longitude` | `40.0000` / `-73.0000` |
| `bedrooms` / `bathrooms` | `3` / `2` |
| `floorSpace` | `"1,800 sqft"` |
| `lotSize` | `"0.25 acres"` |
| `listingAgentName` | `"Agent Name"` |
| `brokerName` | `"Sample Realty"` |
| `heroImage` | listing's main photo URL |
| `photos` | array of gallery photo URLs |
| `tags` | `["NEW", "OPEN SUN"]` (status badges) |
| `dateListed` | `"2026-01-01"` |
| `description` | listing description text |
| `scrapedAt` | `"2026-01-01T00:00:00.000Z"` |

With **Fetch property details** enabled, each record also gains `priceHistory`, `taxes`, `features`, `openHouses`, `providerDetails` (agent and broker contact), `localProtections`, `affordability`, `estimatedRent`, `totalPhotoCount`, `detailFetched` (whether this record's own detail fetch actually succeeded this run), plus the full `homeDetails` object.

**Incremental mode only.** When `incrementalMode` is on, a returned record also carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields` (populated only for `UPDATED`), `firstSeenAt` and `lastSeenAt`. An `EXPIRED` row is a tombstone built from the last snapshot this actor collected, not a fresh re-fetch of a listing that is gone.

### How to Use

1. Pick a **Start mode**: search by location, or paste direct Trulia URLs.
2. In search mode, fill in Location, Listing type and optionally Keyword and Sort; in URL mode, paste one or more Trulia search or property links.
3. Turn on **Fetch property details** if you need price history, taxes, features or agent contact, and set **Max items** to control run size and cost.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**Basic search:**

```json
{
  "mode": "search",
  "searchLocation": "Austin,TX",
  "searchListingType": "FOR_SALE",
  "maxItems": 50
}
```

**Search with keyword and sort:**

```json
{
  "mode": "search",
  "searchLocation": "Miami,FL",
  "searchListingType": "FOR_SALE",
  "searchKeyword": "pool",
  "searchSort": "PRICE_LOW_TO_HIGH",
  "maxItems": 100
}
```

**For rent, with full detail enrichment:**

```json
{
  "mode": "search",
  "searchLocation": "Seattle,WA",
  "searchListingType": "FOR_RENT",
  "fetchDetails": true,
  "maxItems": 40
}
```

**Direct URLs (search URLs paginate, property URLs return one record each):**

```json
{
  "mode": "url",
  "startUrls": [
    "https://www.trulia.com/sold/Denver,CO/",
    "https://www.trulia.com/home/sample-address-co-80000-00000002"
  ],
  "maxItems": 60
}
```

**Supported URL shapes (URL mode).** Paste any of these; search shapes paginate automatically and property shapes return one record each. `m.trulia.com` links are converted to `www.trulia.com`.

| Shape | Example |
|---|---|
| for sale | `https://www.trulia.com/for_sale/Austin,TX/` |
| for rent | `https://www.trulia.com/for_rent/Austin,TX/` |
| sold | `https://www.trulia.com/sold/Austin,TX/` |
| paginated | `https://www.trulia.com/for_rent/Austin,TX/2_p/` |
| keyword | `https://www.trulia.com/for_sale/Austin,TX/pool_keyword/` |
| sort price asc | `https://www.trulia.com/for_sale/Austin,TX/price;a_sort/` |
| sort plus page | `https://www.trulia.com/for_sale/Austin,TX/2_p/price;a_sort/` |
| beds | `https://www.trulia.com/for_sale/Austin,TX/2p_beds/` |
| type | `https://www.trulia.com/for_sale/Austin,TX/SINGLE-FAMILY_HOME_type/` |
| state city canonical | `https://www.trulia.com/TX/Austin/` |
| neighborhood | `https://www.trulia.com/TX/Austin,Downtown/` |
| zip canonical | `https://www.trulia.com/TX/Austin/78701/` |
| zip legacy | `https://www.trulia.com/for_sale/78701_zip/` |
| mobile host | `https://m.trulia.com/for_sale/Austin,TX/` |
| home detail | `https://www.trulia.com/home/11221-gadsen-ln-105-austin-tx-78754-338244892` |
| legacy p detail | `https://www.trulia.com/p/tx/austin/11221-gadsen-ln-105-austin-tx-78754--338244892` |
| building detail | `https://www.trulia.com/building/33hundred-3300-wells-branch-pkwy-austin-tx-78728-1146835547` |
| paginated sold | `https://www.trulia.com/sold/Austin,TX/2_p/` |
| paginated canonical | `https://www.trulia.com/TX/Austin/2_p/` |
| state page | `https://www.trulia.com/TX/` |
| new homes | `https://www.trulia.com/for_sale/Austin,TX/new_homes_lt/` |
| feature landing | `https://www.trulia.com/for_sale/Austin,TX/backyard/` |
| price range plus beds plus sort | `https://www.trulia.com/for_sale/Austin,TX/0-500000_price/3p_beds/date;d_sort/` |
| canonical price landing | `https://www.trulia.com/TX/Austin/homes-for-sale-under-500000/` |
| query sort | `https://www.trulia.com/for_sale/Austin,TX/?sort=newest` |
| query sort (price) | `https://www.trulia.com/for_sale/Austin,TX/?sort=price_desc` |
| keyword plus sort plus page | `https://www.trulia.com/for_sale/Austin,TX/pool_keyword/price;a_sort/2_p/` |

Canonical state, city, neighborhood and ZIP pages carry no listing type and are read as for-sale. A `?query` (for example `?sort=newest`) is kept and the page segment is placed in the path in front of it (`/2_p/?sort=newest`). The Trulia home page (`trulia.com`) and bare feature paths such as `trulia.com/backyard/` are not listing searches and are skipped with a warning; use `/for_sale/<place>/backyard/` instead. A page segment in front of a sort (`2_p/price;a_sort/`) is honored and the walk continues from that page. A URL that is not a supported Trulia shape (another site, or `/home/<numeric-id>` which Trulia answers with HTTP 410) is skipped with a warning in the log. **Default behaviour:** if you switch to URL mode and leave **Trulia URLs** untouched, the actor now scrapes the 17 example URLs above (split across Max items) instead of failing with no URLs; clear the list and paste your own for a normal run. In search mode, the Location accepts `Austin, TX`, `Austin TX` or `Austin,TX`; a location that returns nothing logs an error.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/trulia-scraper").call(run_input={
    "mode": "search",
    "searchLocation": "Austin,TX",
    "searchListingType": "FOR_SALE",
    "maxItems": 50,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["streetAddress"], listing["formattedPrice"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/trulia-scraper').call({
  mode: 'search',
  searchLocation: 'Austin,TX',
  searchListingType: 'FOR_SALE',
  maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Max items and Max pages

**Max items** is the run's overall cap on listings. When you paste several URLs, it is split evenly between them (each URL gets Max items divided by the number of URLs, rounded up), so every URL is represented. **Max pages** is an optional extra safety cap on pages walked per search or URL (Trulia serves 40 listings per page); leave it empty to walk every result page and let Max items decide when to stop.

Listings are deduplicated once across the whole run, not per URL, so the same listing is never returned twice even if two of your URLs overlap. Known limitation, URL mode with two or more URLs only: when a URL's share of Max items runs out partway through a results page, the listings on that page that were not collected are still marked as already seen for the rest of the run. If a later URL in the same run reaches one of those listings (two overlapping search areas, or a property URL for a home that appeared on that cut page), it is skipped without a warning and never returned by either URL, so the run can end with fewer listings than Max items allows. Raising Max items does not reliably avoid this, because a URL's share almost always ends mid-page. To avoid it, set Max items to `0` (unlimited) and bound the run with Max pages instead, or run overlapping URLs as separate runs. Search mode is not affected, because it runs one search per run.

#### Resume and recurring updates

**Resume** (`resumeFromRunId`) continues one specific interrupted or previous run: paste its run ID or dataset ID and this run skips listings already collected there, returning only what is left. Because Resume reads the listings actually saved in that earlier dataset, it is not affected by the limitation above. An automatic same-run checkpoint also lets a run continue after a platform migration or the Resurrect button, without any input needed. That checkpoint stores the run's "already seen" list, so it carries the limitation above into the continued run, and listings from the page being processed at the exact moment of a migration can be marked as seen before they were saved and then skipped. If a migrated or resurrected run looks short, start a new run with Resume pointed at it to collect only what is missing.

**Incremental mode** (`incrementalMode`) is for scheduling the same search or URL set repeatedly, for example daily. The actor remembers the previous run of that same search by itself, so no run ID needs to be pasted. The first run returns everything as `NEW`; later runs return only `NEW`, `UPDATED` and `REAPPEARED` listings by default, and duplicates or unchanged listings are suppressed and not billed. Turn on `emitUnchanged` or `emitExpired` only when you also want those rows returned, and billed for. State is scoped per location or URL, listing type, keyword, sort and the `fetchDetails` setting automatically; set `stateKey` to name or deliberately share a monitoring campaign. With `fetchDetails` on, a listing's property page is still fetched every run so change detection stays accurate, but the enrichment charge only applies to listings actually returned; a suppressed unchanged listing is neither pushed nor billed.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. If the picker is empty, you have not authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` builds Trulia search URLs from the filters below; `url` takes Trulia search or property URLs you already have. |
| `searchLocation` | string | `New_York,NY` | Search mode only. A US location as City,STATE. Spaces are converted to underscores automatically. |
| `searchListingType` | string | `FOR_SALE` | Search mode only. `FOR_SALE`, `FOR_RENT`, or `SOLD`. |
| `searchKeyword` | string | (empty) | Search mode only. Narrows results to listings whose text mentions this keyword. Leave empty for no keyword filter. |
| `searchSort` | string | `RECOMMENDED` | Search mode only. `RECOMMENDED`, `NEWEST`, `PRICE_LOW_TO_HIGH`, or `PRICE_HIGH_TO_LOW`. `NEWEST` is currently served in the recommended order because Trulia no longer offers a newest-first order (the run logs a warning); use `incrementalMode` to catch new listings. Trulia may place a few promoted listings ahead of the chosen order. |
| `minBeds` | integer | (empty) | Search mode only. Keep listings with at least this many bedrooms (1-5). |
| `minBaths` | integer | (empty) | Search mode only. Keep listings with at least this many bathrooms (1-4). Trulia applies this filter on its side and counts half-baths, so a listing with one and a half baths can appear under 2 or more. It works with or without `minBeds`. |
| `propertyType` | string | (empty) | Search mode only. `SINGLE_FAMILY`, `CONDO`, `TOWNHOUSE`, `MULTI_FAMILY`, `LAND` or `MOBILE_MANUFACTURED`. |
| `searchZip` | string | (empty) | Search mode only. 5-digit ZIP code; replaces `searchLocation` when set. |
| `startUrls` | array | the 17 example URLs above | URL mode only. Trulia search URLs (paginated automatically) or property/home URLs (fetched as single detail records). Multiple URLs supported. |
| `fetchDetails` | boolean | `false` | Include price history, tax history, the full feature list, open houses, listing agent and broker contact, affordability and more from the property page. Billed per enriched listing that is actually returned. |
| `maxItems` | integer | `20` | Maximum number of listings to scrape across all searches and URLs in this run. Set `0` for unlimited. |
| `maxPages` | integer | (unlimited) | Optional safety cap on search pages to walk per location or URL (40 listings per page). Leave empty to walk every result page; the run still stops at Max items. |
| `resumeFromRunId` | string | (none) | Paste a previous run ID or dataset ID from this actor to continue a large crawl without returning or charging for listings already collected there. For recurring monitoring of the same search, use Incremental mode instead. |
| `incrementalMode` | boolean | `false` | Turn on for daily or recurring monitoring. The first run returns all matching listings as `NEW`. Later runs normally return only `NEW`, `UPDATED`, and `REAPPEARED` listings. `UPDATED` means any field of the stored record changed; only the relative "last checked" text and the MLS refresh stamp inside the listing source are never compared, plus any field you list in `ignoreFieldsForChanges`. |
| `stateKey` | string | (none) | Optional. Names this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Left empty, the actor derives a key automatically from the location or URLs, listing type, keyword, sort, and `fetchDetails` settings. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill for) listings unchanged since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return (and bill for) listings from a previous run no longer found, marked `EXPIRED`, once a run has fully scanned the search (not capped, not a resume, not a pasted page-N start, not trimmed by Max pages). |
| `changeScope` | string | `all` | Incremental mode only. `all` compares the whole record. `priceAndStock` compares only price, price type, currency, listing type, current status and the price-change block, so description, photo, tag or agent changes never return an `UPDATED` row. |
| `compactUpdates` | boolean | `false` | Incremental mode only. `UPDATED` and `EXPIRED` rows become short (ids, url, location, current price and status, `priceChanges`, `variantChanges`). `NEW` rows stay complete. The Fetch details charge does not apply to a compact row, because it carries no detail data. |
| `expireAfterMissedScans` | integer | `1` | Incremental mode with `emitExpired` only. A listing is marked `EXPIRED` only after it was missing from this many complete scans in a row. `EXPIRED` means no longer found in the results, not proof of delisting; use the status fields for availability. |
| `ignoreFieldsForChanges` | array | (none) | Incremental mode only. Field names (dotted for nested fields, for example `provider.logoUrl`) excluded from `UPDATED` detection and `changedFields`. The fields stay in the output. |
| `proxy` | object | Apify proxy, United States | Connection settings. The default reads search results and home pages over a datacenter connection pinned to the United States; building pages use Apify Residential. Neither connection is accepted on every try, so the actor retries on other exits. You can select Apify Residential (US) or supply your own proxy URLs. Saved inputs that already select Residential keep it. |
| `mcpConnectors` | array | (none) | Optionally send a summary of each record to apps you authorized under Integrations (Notion, Linear, Airtable, or Apify). |
| `notionParentPageUrl` | string | (none) | Notion connector only. URL or id of the Notion page under which item pages are created. Ignored by other connectors. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run, from 1 to 1000. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": "00000001",
  "url": "https://www.trulia.com/home/sample-address-ny-10000-00000001",
  "listingType": "FOR_SALE",
  "propertyType": "SINGLE_FAMILY_HOME",
  "price": 750000,
  "formattedPrice": "$750,000",
  "streetAddress": "123 Sample Street",
  "city": "Sampletown",
  "state": "NY",
  "zipCode": "10000",
  "neighborhood": "Sample Heights",
  "latitude": 40.0,
  "longitude": -73.0,
  "bedrooms": 3,
  "bathrooms": 2,
  "floorSpace": "1,800 sqft",
  "lotSize": "0.25 acres",
  "listingAgentName": "Agent Name",
  "brokerName": "Sample Realty",
  "heroImage": "https://www.trulia.com/pictures/thumbs_4/sample/00000000-full.jpg",
  "photos": ["https://www.trulia.com/pictures/thumbs_6/sample/00000000-full.jpg"],
  "tags": ["NEW"],
  "dateListed": "2026-01-01",
  "description": "Sample listing description.",
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

#### Output views

The dataset has three views in the Console: **Overview** (price, address, size, status and agent for every record), **Detail** (price and tax history, features, open houses and agent contact; filled when Fetch property details is on) and **Changes** (`changeType`, `changedFields` and first/last seen; filled in Incremental mode).

#### Pricing

You pay per listing returned. When **Fetch property details** is on, an additional detail-enrichment event is charged once per returned listing that actually carries detail data. A compact `UPDATED` or `EXPIRED` row, a listing suppressed as unchanged or duplicated, and a failed fetch are never charged. The **Pricing** tab shows the current rates; **Max items** caps the spend of any run.

### Plan Requirement

Datacenter and residential connections both work, but neither is guaranteed on every request. In our measurements the site checks the connection first and then refuses some exits at random: search pages and `/home/` pages loaded over the default Apify datacenter connection pinned to the United States, `/building/` pages were refused on datacenter exits and loaded over Apify Residential, and a residential exit was itself refused on some tries. The actor therefore reads search and home pages on the default connection, switches to Apify Residential for building pages, and retries on other exits before giving up, so you do not need to pick anything for a normal run. Apify free plans do not include Apify proxy, so on a free plan the actor can only use your own proxy URLs or its backup connection. If the selected connection is unavailable, the log shows:

> The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.

and, once a page is served that way: "Served over the backup connection (the default connection was unavailable)."

If no page can be read at all, the run ends as failed with one of these reasons:

- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Trulia pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

Behaviour change: the default used to be Residential (US); runs whose saved input already selects Residential are unchanged. Proxy traffic is billed to your Apify account.

### FAQ

#### How much does it cost?

See the Pricing section above for what is charged and when; the **Pricing** tab shows the current rates.

#### Is it legal to scrape Trulia?

This actor collects only publicly available real estate listing data. You are responsible for how you use it: follow Trulia's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts (price, address, specs) are generally factual data, but photos and some agent/broker content may be subject to third-party rights.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab with the same search or URL input and turn on **Incremental mode**. Each run then returns only new, updated and reappeared listings, and unchanged ones are not billed.

#### Why did my run return fewer listings than I expected?

The most common reason is **Max items**: raise it, or set it to `0` for unlimited, to walk every available result page. **Max pages** is a separate, optional cap on pages per search or URL; leave it empty to remove it. A search or keyword that is genuinely narrow can also just have few results on Trulia itself.

#### Why is a listing sometimes missing even though it is on Trulia?

Two common causes. First, the selected connection may be refused by the site on some exits; try again or select Apify Residential (US). Second, in URL mode with several overlapping URLs, when one URL's share of Max items ends partway through a results page, the uncollected listings on that page are still treated as already seen for the rest of the run. If another URL in the same run reaches one of them, it is skipped without a warning rather than returned. Set Max items to `0` and use Max pages as the bound, or run overlapping URLs as separate runs, to avoid it. A run continued after a platform migration or Resurrect can also miss a few listings from the page in progress at that moment; a follow-up run with Resume pointed at it collects them.

#### Why did my run fail instead of returning an empty dataset?

If nothing could be read at all (for example, every request was refused), the run stops with a clear message so "no listings match" is never confused with "nothing could be read." Run it again in a few minutes, and if it keeps happening, select Apify Residential (US) in the Proxy & connection field.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/homes-com-scraper"><b>Homes.com Scraper</b></a><br>Scrape US property records from Homes.com. Pick a city, state or ZIP and a channel (for...</td><td>🏠 <a href="https://apify.com/abotapi/redfin-scraper"><b>Redfin Scraper</b></a><br>Scrape redfin.com home listings: price, beds, baths, sqft, lot, year built, coordinates...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/zumper-scraper"><b>Zumper Scraper</b></a><br>Scrape Zumper.com rental listings: apartments, houses, condos and rooms. Pulls 60+ fields...</td><td>🏠 <a href="https://apify.com/abotapi/realtor-com-scraper"><b>Realtor.com</b></a><br>Extract property listings from realtor.com. Get comprehensive data, including prices...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/realtor-ca-scraper"><b>Realtor.ca</b></a><br>Scrape Realtor.ca property listings with prices, descriptions, images, agent and...</td><td>🧩 <a href="https://apify.com/abotapi/zillow-scraper"><b>Zillow Scraper</b></a><br>Scrape zillow.com properties for sale, for rent and recently sold across the US: price...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/trulia-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

How to start. 'Search by location' builds Trulia search URLs from the filters below. 'Direct URLs' takes Trulia search or property URLs you already have.

## `searchLocation` (type: `string`):

Search mode only. A US location such as 'Austin, TX', 'Austin TX', Austin,TX or New_York,NY. Spaces are converted automatically; a location that matches nothing fails loudly.

## `searchListingType` (type: `string`):

Search mode only. For sale, for rent, or recently sold listings.

## `searchKeyword` (type: `string`):

Search mode only. Narrows results to listings whose text mentions this keyword (e.g. pool, waterfront, garage). Leave empty for no keyword filter.

## `searchSort` (type: `string`):

Search mode only. Result ordering. Newest is currently served in the recommended order because the site no longer offers a newest-first order; use incremental mode to catch new listings. Trulia may place a few promoted listings ahead of the chosen order.

## `minBeds` (type: `integer`):

Search mode only. Keep listings with at least this many bedrooms (1-5). Leave empty for any.

## `minBaths` (type: `integer`):

Search mode only. Keep listings with at least this many bathrooms (1-4). Trulia applies this filter on its side and counts half-baths, so a listing with one and a half baths can appear under 2 or more. Works with or without Minimum bedrooms. Leave empty for any.

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

Search mode only. Restrict to one property type. Leave empty for all.

## `searchZip` (type: `string`):

Search mode only. 5-digit US ZIP code. When set it replaces the location field.

## `startUrls` (type: `array`):

URL mode only. Trulia search URLs (for sale, for rent, sold, state/city, neighborhood, ZIP, filtered, sorted, paginated, m.trulia.com) or property pages (/home/, /p/, /building/). Paginated automatically; property URLs return one record each. Left untouched, URL mode scrapes these 17 example URLs.

## `fetchDetails` (type: `boolean`):

Include full property details: price history, tax history, full feature list, open houses, listing agent and broker contact, affordability and more. Slower and uses more proxy traffic; billed per enriched listing. Leave off for fast search-result data, which already includes price, beds, baths, address, GPS, photos and agent name.

## `maxItems` (type: `integer`):

Maximum number of listings to scrape across all searches/URLs. This is the run cap. Set 0 for unlimited.

## `maxPages` (type: `integer`):

Optional safety cap on search pages to walk per location/URL (40 listings per page). Leave empty to walk every result page; the run stops at Max items.

## `proxy` (type: `object`):

Datacenter exits (the default) read search results and home pages. Building pages need a residential exit; the actor switches to Apify Residential for those and can fall back to a backup connection if the selected one is unavailable. You can pick Apify Residential (US) yourself, or supply your own proxy URLs. Free Apify plans do not include Apify proxy.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID (or dataset ID) from this actor to continue a large crawl without returning or charging for listings already collected there. Use this after an interrupted run. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED listings. UPDATED means any field of the stored listing record changed (price, status, description, photos, agent, tags and so on); the only things never compared are the relative "last checked ... ago" text and the MLS refresh stamp inside the listing source, plus any field you list in Ignore fields. Use What counts as a change to compare price and status only. Turn on "Emit unchanged" or "Emit expired" only when you also want those listings returned (and billed). State is kept separately per location/URL, listing type, keyword, sort, and fetchDetails setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the location/URLs, listing type, keyword, sort, and fetchDetails settings -- different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. This returns -- and bills -- extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED, once a run has fully scanned the search (not capped, not a resume). Adds and bills extra synthetic rows, so leave it off unless you need expiry tracking.

## `changeScope` (type: `string`):

All fields (default): any change to the listing record, except the always-ignored relative-time text inside the listing source ("last checked ... ago") and the MLS refresh stamp, returns an UPDATED row. Price and status only: only a change to price, formatted price, price type, currency, listing type, current status (active, pending, sold, off market) or the price-change block returns an UPDATED row; descriptions, photos, tags, agent details and other fields never do. Changing this option on a search with no State key continues from that search's saved baseline instead of starting over.

## `compactUpdates` (type: `boolean`):

Off by default. When on, UPDATED rows (and EXPIRED rows) are short: listing id, url, location, current price and status, plus priceChanges (old and new value of each moved price or status field) and variantChanges (per-unit changes when a listing carries units). NEW rows always carry the complete record. The Fetch details enrichment charge does not apply to a compact row because it no longer carries the detail data.

## `expireAfterMissedScans` (type: `integer`):

Only used with Emit expired listings. A listing is marked EXPIRED only after it has been missing from this many complete scans in a row (default 1). Raise it (for example 3) so a listing that briefly drops out of the results is not reported as gone. A listing that comes back resets the count. EXPIRED means the listing was no longer found in the results; it does not prove the property was delisted, so check the status fields for availability.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to exclude from UPDATED change detection and from changedFields, in addition to the built-in handling (the relative "last checked" text and MLS refresh stamp already never trigger an UPDATED). Use this if a field in your setup turns out to be noisy. Nested fields use dotted names, for example provider.logoUrl or homeDetails.activeListing.provider. The fields stay in the output rows; they just do not decide UPDATED.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (address + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "searchLocation": "New_York,NY",
  "searchListingType": "FOR_SALE",
  "searchSort": "RECOMMENDED",
  "startUrls": [
    "https://www.trulia.com/for_sale/Austin,TX/",
    "https://www.trulia.com/for_rent/Austin,TX/",
    "https://www.trulia.com/sold/Austin,TX/",
    "https://www.trulia.com/for_rent/Austin,TX/2_p/",
    "https://www.trulia.com/for_sale/Austin,TX/pool_keyword/",
    "https://www.trulia.com/for_sale/Austin,TX/price;a_sort/",
    "https://www.trulia.com/for_sale/Austin,TX/?sort=price_desc",
    "https://www.trulia.com/for_sale/Austin,TX/2p_beds/",
    "https://www.trulia.com/for_sale/Austin,TX/SINGLE-FAMILY_HOME_type/",
    "https://www.trulia.com/TX/Austin/",
    "https://www.trulia.com/TX/Austin,Downtown/",
    "https://www.trulia.com/TX/Austin/78701/",
    "https://www.trulia.com/for_sale/78701_zip/",
    "https://m.trulia.com/for_sale/Austin,TX/",
    "https://www.trulia.com/home/11221-gadsen-ln-105-austin-tx-78754-338244892",
    "https://www.trulia.com/p/tx/austin/11221-gadsen-ln-105-austin-tx-78754--338244892",
    "https://www.trulia.com/building/33hundred-3300-wells-branch-pkwy-austin-tx-78728-1146835547"
  ],
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "changeScope": "all",
  "compactUpdates": false,
  "expireAfterMissedScans": 1,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `summary` (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 = {
    "mode": "search",
    "searchLocation": "New_York,NY",
    "startUrls": [
        "https://www.trulia.com/for_sale/Austin,TX/",
        "https://www.trulia.com/for_rent/Austin,TX/",
        "https://www.trulia.com/sold/Austin,TX/",
        "https://www.trulia.com/for_rent/Austin,TX/2_p/",
        "https://www.trulia.com/for_sale/Austin,TX/pool_keyword/",
        "https://www.trulia.com/for_sale/Austin,TX/price;a_sort/",
        "https://www.trulia.com/for_sale/Austin,TX/?sort=price_desc",
        "https://www.trulia.com/for_sale/Austin,TX/2p_beds/",
        "https://www.trulia.com/for_sale/Austin,TX/SINGLE-FAMILY_HOME_type/",
        "https://www.trulia.com/TX/Austin/",
        "https://www.trulia.com/TX/Austin,Downtown/",
        "https://www.trulia.com/TX/Austin/78701/",
        "https://www.trulia.com/for_sale/78701_zip/",
        "https://m.trulia.com/for_sale/Austin,TX/",
        "https://www.trulia.com/home/11221-gadsen-ln-105-austin-tx-78754-338244892",
        "https://www.trulia.com/p/tx/austin/11221-gadsen-ln-105-austin-tx-78754--338244892",
        "https://www.trulia.com/building/33hundred-3300-wells-branch-pkwy-austin-tx-78728-1146835547"
    ],
    "maxItems": 20,
    "maxPages": 0,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "changeScope": "all",
    "compactUpdates": false,
    "expireAfterMissedScans": 1,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/trulia-scraper").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 = {
    "mode": "search",
    "searchLocation": "New_York,NY",
    "startUrls": [
        "https://www.trulia.com/for_sale/Austin,TX/",
        "https://www.trulia.com/for_rent/Austin,TX/",
        "https://www.trulia.com/sold/Austin,TX/",
        "https://www.trulia.com/for_rent/Austin,TX/2_p/",
        "https://www.trulia.com/for_sale/Austin,TX/pool_keyword/",
        "https://www.trulia.com/for_sale/Austin,TX/price;a_sort/",
        "https://www.trulia.com/for_sale/Austin,TX/?sort=price_desc",
        "https://www.trulia.com/for_sale/Austin,TX/2p_beds/",
        "https://www.trulia.com/for_sale/Austin,TX/SINGLE-FAMILY_HOME_type/",
        "https://www.trulia.com/TX/Austin/",
        "https://www.trulia.com/TX/Austin,Downtown/",
        "https://www.trulia.com/TX/Austin/78701/",
        "https://www.trulia.com/for_sale/78701_zip/",
        "https://m.trulia.com/for_sale/Austin,TX/",
        "https://www.trulia.com/home/11221-gadsen-ln-105-austin-tx-78754-338244892",
        "https://www.trulia.com/p/tx/austin/11221-gadsen-ln-105-austin-tx-78754--338244892",
        "https://www.trulia.com/building/33hundred-3300-wells-branch-pkwy-austin-tx-78728-1146835547",
    ],
    "maxItems": 20,
    "maxPages": 0,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "changeScope": "all",
    "compactUpdates": False,
    "expireAfterMissedScans": 1,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/trulia-scraper").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 '{
  "mode": "search",
  "searchLocation": "New_York,NY",
  "startUrls": [
    "https://www.trulia.com/for_sale/Austin,TX/",
    "https://www.trulia.com/for_rent/Austin,TX/",
    "https://www.trulia.com/sold/Austin,TX/",
    "https://www.trulia.com/for_rent/Austin,TX/2_p/",
    "https://www.trulia.com/for_sale/Austin,TX/pool_keyword/",
    "https://www.trulia.com/for_sale/Austin,TX/price;a_sort/",
    "https://www.trulia.com/for_sale/Austin,TX/?sort=price_desc",
    "https://www.trulia.com/for_sale/Austin,TX/2p_beds/",
    "https://www.trulia.com/for_sale/Austin,TX/SINGLE-FAMILY_HOME_type/",
    "https://www.trulia.com/TX/Austin/",
    "https://www.trulia.com/TX/Austin,Downtown/",
    "https://www.trulia.com/TX/Austin/78701/",
    "https://www.trulia.com/for_sale/78701_zip/",
    "https://m.trulia.com/for_sale/Austin,TX/",
    "https://www.trulia.com/home/11221-gadsen-ln-105-austin-tx-78754-338244892",
    "https://www.trulia.com/p/tx/austin/11221-gadsen-ln-105-austin-tx-78754--338244892",
    "https://www.trulia.com/building/33hundred-3300-wells-branch-pkwy-austin-tx-78728-1146835547"
  ],
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "changeScope": "all",
  "compactUpdates": false,
  "expireAfterMissedScans": 1,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/trulia-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/trulia-scraper"
        }
    }
}
```

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/MwwMDSky7SLgeJkR8/builds/j9YzTAW7YRrfsZjQo/openapi.json
