# StreetEasy Listing Monitor (`automation-lab/streeteasy-listing-monitor`) Actor

Snapshot and monitor public StreetEasy NYC sale and rental searches with normalized prices, status, days on market, broker, and change metadata.

- **URL**: https://apify.com/automation-lab/streeteasy-listing-monitor.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## StreetEasy Listing Monitor

StreetEasy Listing Monitor turns public NYC sale and rental searches into stable, normalized listing records.

Use it as a StreetEasy scraper for one-time apartment research or schedule repeat runs to identify new, repriced, status-changed, updated, and removed listings.

Each record includes a stable listing ID, current price, source status, source-derived days on market, broker label, property facts, coordinates, and observation metadata.

The Actor supports public borough and all-NYC search URLs for rentals and sales.

It does not require a StreetEasy account.

### What does StreetEasy Listing Monitor do?

The Actor accepts one or more public StreetEasy search URLs.

It queries the same structured listing service used by the public site, normalizes sale and rental results, and stores them in the default Apify dataset.

For every accepted listing it also retrieves the source creation timestamp used to calculate `daysOnMarket`.

With monitoring enabled, the Actor stores a private snapshot under a hash of your `stateKey`.

Later runs compare the current snapshot with that prior state.

The result is ready for spreadsheets, databases, dashboards, webhooks, and scheduled market-monitoring workflows.

### Who is it for?

- Real estate analysts tracking NYC asking-price and inventory movements.
- Buyer agents building current sale comps for a borough.
- Rental teams monitoring new StreetEasy apartments in Brooklyn or Manhattan.
- Investors watching listings for price or status changes.
- Data teams that need consistent records instead of page-specific HTML.
- Developers building a private StreetEasy data pipeline through the Apify API.

This Actor is designed for inventory observation.

It does not contact brokers, submit forms, log in, or collect private account data.

### Why use normalized listing snapshots?

StreetEasy pages are useful for browsing, but repeated analysis benefits from stable fields.

The Actor uses `listingType + listingId` as the comparison identity.

It records both source timestamps and observation timestamps.

That distinction helps answer two different questions:

1. When did StreetEasy create or update this listing?
2. When did my own workflow first and most recently observe it?

The `sourceFingerprint` hashes change-relevant fields so downstream systems can deduplicate observations.

### Supported StreetEasy search URLs

Use HTTPS search pages for all NYC or one borough.

Supported rental forms include:

- `https://streeteasy.com/for-rent/nyc`
- `https://streeteasy.com/for-rent/manhattan`
- `https://streeteasy.com/for-rent/brooklyn`
- `https://streeteasy.com/for-rent/queens`
- `https://streeteasy.com/for-rent/bronx`
- `https://streeteasy.com/for-rent/staten-island`

Replace `for-rent` with `for-sale` for sale inventory.

Unsupported hosts, listing detail URLs, building pages, arbitrary neighborhood slugs, and non-HTTPS URLs fail closed with a clear input error.

### What StreetEasy data is extracted?

| Field | Meaning |
| --- | --- |
| `listingId` | Stable StreetEasy listing ID |
| `listingType` | `rent` or `sale` |
| `url` | Canonical public listing URL |
| `street`, `unit` | Source address and unit |
| `areaName` | StreetEasy neighborhood label |
| `price`, `currency` | Current asking price or monthly rent in USD |
| `priceDelta` | Latest source-reported price change when available |
| `status` | Current source listing status |
| `listedAt` | Source listing creation timestamp |
| `updatedAt` | Latest source update timestamp |
| `daysOnMarket` | Whole days since `listedAt` |
| `bedrooms` | Bedroom count |
| `bathrooms`, `halfBathrooms` | Full and half bathroom counts |
| `livingAreaSquareFeet` | Reported living area when available |
| `buildingType` | Source building classification |
| `brokerName` | Brokerage or source-group label |
| `sourceType` | StreetEasy ingestion source classification |
| `latitude`, `longitude` | Listing coordinates |
| `sourceSearchUrl` | Input URL that produced the record |
| `sourcePage` | One-based result page; zero for removed records |
| `observedAt` | Current run observation timestamp |
| `firstSeenAt` | First observation timestamp for this monitor |
| `changeType` | Comparison classification |
| `previousPrice`, `previousStatus` | Prior monitor values |
| `sourceFingerprint` | Hash of change-relevant source fields |

Source fields may be `null` when StreetEasy does not provide a value.

### Getting started

1. Open the Actor input page.
2. Add a supported StreetEasy NYC sale or rental search URL.
3. Set `maxItems` to the number of records you need.
4. Keep `maxPages` small for an initial run.
5. Click **Start**.
6. Open the default dataset and select the **StreetEasy listings** view.
7. Export JSON, CSV, Excel, XML, or RSS as needed.

A useful first input is:

```json
{
  "startUrls": [
    { "url": "https://streeteasy.com/for-rent/brooklyn" }
  ],
  "maxItems": 20,
  "maxPages": 1
}
```

### Input parameters

#### `startUrls`

Required array of supported public StreetEasy search URLs.

You can mix sale and rental searches in one run.

#### `maxItems`

Maximum number of output records across all input searches.

Default: `50`.

Allowed range: `1` to `5000`.

#### `maxPages`

Maximum structured result pages fetched for each search.

Default: `10`.

Allowed range: `1` to `100`.

#### `monitoring`

Set to `true` to compare the current inventory with stored prior state.

#### `stateKey`

Required when monitoring is enabled.

Choose a stable private value for one logical monitor, such as `brooklyn-rental-watch`.

The Actor hashes this value before constructing the key-value store key.

#### `emitUnchanged`

Include `UNCHANGED` records in monitoring runs.

It defaults to `false` so scheduled runs can produce a compact change feed.

#### `emitRemoved`

Emit `REMOVED` records that existed in a prior complete snapshot but are absent now.

Removal detection is skipped when `maxItems` or `maxPages` truncates the current snapshot, preventing false removals.

### Output example

A current rental record has this shape:

```json
{
  "listingId": "5118281",
  "listingType": "rent",
  "url": "https://streeteasy.com/building/97-grand-avenue-brooklyn/4b",
  "street": "97 Grand Avenue",
  "unit": "4B",
  "areaName": "Clinton Hill",
  "price": 4565,
  "currency": "USD",
  "status": "ACTIVE",
  "daysOnMarket": 0,
  "listedAt": "2026-07-30T01:05:02.000-04:00",
  "bedrooms": 2,
  "bathrooms": 2,
  "brokerName": "Brighton Management",
  "sourceSearchUrl": "https://streeteasy.com/for-rent/brooklyn",
  "changeType": "NEW",
  "previousPrice": null,
  "observedAt": "2026-07-30T05:29:23.554Z"
}
```

The full dataset also includes update timestamps, coordinates, source classifications, comparison metadata, and the source fingerprint.

### How does change monitoring work?

The first run for a new `stateKey` classifies observed listings as `NEW`.

Later runs use these values:

- `NEW`: not present in prior state.
- `PRICE_CHANGED`: current price differs from prior price.
- `STATUS_CHANGED`: source status differs from prior status.
- `UPDATED`: another fingerprinted source field changed.
- `UNCHANGED`: no fingerprinted field changed.
- `REMOVED`: absent from a complete current snapshot.

Use one `stateKey` per independent search set.

Reusing the same key for unrelated URLs merges their comparison state.

### Schedule a StreetEasy price and status monitor

Create an Apify Schedule after a successful first run.

Hourly or daily frequency is usually more appropriate than rapid polling.

A change-feed input can look like this:

```json
{
  "startUrls": [
    { "url": "https://streeteasy.com/for-sale/manhattan" }
  ],
  "maxItems": 500,
  "maxPages": 10,
  "monitoring": true,
  "stateKey": "manhattan-sale-watch",
  "emitUnchanged": false,
  "emitRemoved": true
}
```

Connect the schedule to a webhook, Make, Zapier, Slack workflow, or database loader.

Treat `REMOVED` as an observed absence, not proof that a property sold.

### How much does it cost to monitor StreetEasy listings?

The Actor uses pay-per-event pricing.

Each run has a one-time `$0.005` start event.

Each accepted listing or change record uses the `item` event.

The BRONZE item price is `$0.0018` before higher-plan volume-tier discounts.

At that price, 20 records cost about `$0.041`, 100 records about `$0.185`, and 1,000 records about `$1.805` on BRONZE.

These examples cover Actor event charges.

Your Apify plan and platform usage accounting may also apply.

The Console shows the final charge for every run.

No separate event is charged for detail enrichment, monitoring state, or removed records.

### Efficient and reliable usage

- Start with 10–20 items before increasing limits.
- Keep all URLs for one logical monitor in the same scheduled Task.
- Use `emitUnchanged: false` when downstream systems only need changes.
- Use a sufficiently high limit before enabling removed detection.
- Avoid launching many overlapping runs with the same `stateKey`.
- Keep schedules at a responsible interval.
- Inspect `completeSnapshot` warnings in logs before interpreting removals.

The Actor uses a coherent US residential proxy session because direct and datacenter requests are challenged by the source.

It rotates only after a verified challenge or transient upstream failure.

### Limitations and failure behavior

StreetEasy can change its public structured service or anti-bot controls.

A challenged session is retried with a bounded fresh identity.

The run fails after three blocked sessions rather than returning a false successful empty dataset.

Only public all-NYC and borough search URLs are supported in this version.

Neighborhood-specific path parsing and arbitrary search-filter query parameters are not yet translated into structured filters.

StreetEasy may report zero for unknown living area.

`daysOnMarket` is calculated from the source listing creation timestamp and may not represent cumulative marketing periods across relistings.

Broker names are source-provided labels, not independently verified identities.

Removed records omit property fields that are not retained in monitor state.

### Export and integration patterns

Common workflows include:

1. Export a one-time Brooklyn rental snapshot to CSV.
2. Load Manhattan sale listings into a warehouse for price analysis.
3. Send only `PRICE_CHANGED` records to a Slack webhook.
4. Build a daily borough inventory dashboard.
5. Join stable IDs across runs to calculate your own observed history.
6. Trigger a review when status changes away from `ACTIVE`.

The default dataset is backward-compatible with Apify integrations.

Use the `overview` view for a compact table or fetch all fields through the API.

### Run with the Apify API using cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~streeteasy-listing-monitor/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url":"https://streeteasy.com/for-rent/nyc"}],
    "maxItems": 20,
    "maxPages": 1
  }'
```

Poll the returned run or use the synchronous dataset endpoint when appropriate.

Never commit your Apify token to source control.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/streeteasy-listing-monitor').call({
  startUrls: [{ url: 'https://streeteasy.com/for-sale/manhattan' }],
  maxItems: 50,
  maxPages: 2,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/streeteasy-listing-monitor").call(run_input={
    "startUrls": [{"url": "https://streeteasy.com/for-rent/brooklyn"}],
    "maxItems": 20,
    "maxPages": 1,
})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use StreetEasy data through MCP

Add the Apify MCP server to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/streeteasy-listing-monitor"
```

Claude Desktop, Cursor, and VS Code can use this HTTP MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/streeteasy-listing-monitor"
    }
  }
}
```

Example prompts:

- "Run the StreetEasy listing monitor for 20 Brooklyn rentals and summarize median asking rent."
- "Snapshot 50 Manhattan sale listings and group them by broker."
- "Run my Manhattan sale monitor and show only price or status changes."

MCP calls use your configured Apify identity and consume the same event pricing as Console or API runs.

### Responsible use and legality

This Actor accesses public listing information without logging in.

You are responsible for following StreetEasy terms, Apify policies, applicable database rights, and privacy laws.

Use reasonable schedules and limits.

Do not use the output for harassment, discriminatory housing decisions, unsolicited contact, or attempts to identify private individuals.

Do not assume a public listing is error-free or independently verified.

StreetEasy and its marks belong to their respective owners.

This Actor is an independent tool and is not affiliated with StreetEasy or Zillow Group.

### Troubleshooting

#### Why did the run fail with a blocked-session error?

The source challenged all three bounded US residential sessions.

Wait before retrying and inspect the run log.

Repeated immediate runs can increase challenge rates.

#### Why are no removed records present?

Removal detection requires a prior state and a complete current snapshot.

Increase `maxItems` or `maxPages` until the run is not truncated.

#### Why is `daysOnMarket` null or unexpectedly small?

It depends on StreetEasy's current `createdAt` value.

Relisted inventory may have a new source listing identity and creation time.

#### Why are unchanged records missing?

Monitoring defaults to a compact change feed.

Set `emitUnchanged` to `true` when you need a complete output on every run.

#### Can I use a listing detail URL as input?

No.

This Actor snapshots supported NYC or borough sale/rental search URLs.

### Related Automation Lab Actors

- [Zillow Scraper](https://apify.com/automation-lab/zillow-scraper) for broader US property marketplace workflows.
- [Idealista Listings Monitor](https://apify.com/automation-lab/idealista-listings-monitor) for recurring Spain property inventory snapshots.
- [Trade Me Property Listing Monitor](https://apify.com/automation-lab/trade-me-property-listing-monitor) for New Zealand property listing changes.

Choose one source-specific Actor per marketplace instead of treating records from different platforms as equivalent listings.

### Frequently asked questions

#### Is this a live alerting service?

No.

It produces snapshots and comparison records when a run starts.

Use Apify Schedules and webhooks to create alerts at your chosen interval.

#### Does it support sales and rentals in one run?

Yes.

Add multiple supported `for-sale` and `for-rent` URLs to `startUrls`.

#### Does it expose a StreetEasy API?

It exposes normalized results through the standard Apify API, datasets, clients, and MCP integration.

It does not provide credentials or privileged access to a private StreetEasy API.

#### Does monitoring preserve full history?

The dataset for each run is immutable according to your Apify retention settings.

The private monitor state stores the latest comparison values, not an unlimited event history.

Persist datasets to your warehouse when you need long-term history.

# Actor input Schema

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

Public borough or NYC search pages on StreetEasy, such as /for-rent/brooklyn or /for-sale/manhattan.

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

Stop after this many output records across all searches.

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

Safety limit for GraphQL pagination. Each page contains up to 100 listings.

## `monitoring` (type: `boolean`):

Persist a snapshot and label records NEW, PRICE\_CHANGED, STATUS\_CHANGED, UPDATED, UNCHANGED, or REMOVED on later runs.

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

Stable private name for this search monitor. Required when comparison mode is enabled.

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

Include unchanged records in comparison runs; off by default for a compact change feed.

## `emitRemoved` (type: `boolean`):

Emit prior listings absent from a complete current snapshot. Detection is skipped when limits truncate the snapshot.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://streeteasy.com/for-rent/brooklyn"
    }
  ],
  "maxItems": 20,
  "maxPages": 10,
  "monitoring": false,
  "emitUnchanged": false,
  "emitRemoved": false
}
```

# Actor output Schema

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

Default dataset containing normalized StreetEasy listing observations.

## `state` (type: `string`):

Key-value store containing namespaced monitoring snapshots when enabled.

# 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 = {
    "startUrls": [
        {
            "url": "https://streeteasy.com/for-rent/brooklyn"
        }
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/streeteasy-listing-monitor").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 = {
    "startUrls": [{ "url": "https://streeteasy.com/for-rent/brooklyn" }],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/streeteasy-listing-monitor").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 '{
  "startUrls": [
    {
      "url": "https://streeteasy.com/for-rent/brooklyn"
    }
  ],
  "maxItems": 20
}' |
apify call automation-lab/streeteasy-listing-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/streeteasy-listing-monitor"
        }
    }
}

```

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/JzciEd7nis6m4khHG/builds/QL5AndO3rAUa1Lgbc/openapi.json
