# Stessa Investment Properties Scraper - Rental Data (`growth-installations/stessa-scraper`) Actor

Scrape Stessa's investment-property marketplace (formerly Roofstock) for 37k+ turnkey single-family listings. Extract Roofstock Neighborhood Score, rent estimate with low/high/confidence, HOA, MLS number, listing-agent contact, photo array and CBSA metro code. Daily delta feed.

- **URL**: https://apify.com/growth-installations/stessa-scraper.md
- **Developed by:** [Growth Installations](https://apify.com/growth-installations) (community)
- **Categories:** Real estate, Lead generation, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 property scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## Stessa Investment Properties Scraper - Rental Data

Scrape Stessa's investment-property marketplace (formerly Roofstock) for 37k+ turnkey single-family listings. Extract Roofstock Neighborhood Score, rent estimate with low/high/confidence, HOA, MLS number, listing-agent contact, photo array and CBSA metro code. Daily delta feed.

Built for single-family rental investors, turnkey rental operators, prop-tech analytics teams, buy-box advisors and syndicators, real estate agents prospecting listings by market.

### What this stessa.com scraper extracts

Every run produces one row per record, with change annotations against your last snapshot, with the following fields:

- **property_id** (string), gridV3 identifier (URL-safe)
- **internal_id** (string), Numeric supply id
- **address** (string), Full street address, city, state, zip
- **city** (string)
- **state** (string)
- **postal_code** (string)
- **latitude** (number)
- **longitude** (number)
- **cbsa_code** (string), CBSA metro area code
- **list_price** (number)
- **beds** (integer)
- **baths** (number)
- **sqft** (integer)
- **lot_size** (number)
- **year_built** (integer)
- **property_type** (string)
- **parking_spaces** (integer)
- **has_garage** (boolean)
- **has_pool** (boolean)
- **has_hoa** (boolean)
- **neighborhood_score** (integer), Roofstock's 1-5 Neighborhood Score
- **roofstock_rent** (number), Roofstock's monthly rent estimate
- **roofstock_rent_low** (number)
- **roofstock_rent_high** (number)
- **roofstock_rent_confidence** (string), Confidence level for the rent estimate
- **gross_yield** (number)
- **cap_rate** (number)
- **cash_on_cash** (number)
- **price_per_sqft** (number)
- **monthly_hoa** (number)
- **seller_provided_rent** (number)
- **seller_annual_insurance** (number)
- **seller_provided_tax_rate** (number)
- **listing_agent_name** (string)
- **listing_agent_phone** (string)
- **listing_agent_email** (string)
- **listing_agent_license** (string)
- **listing_agent_company_name** (string)
- **listing_agent_company_phone** (string)
- **listing_agent_company_email** (string)
- **mls_source_name** (string)
- **mls_source_number** (string)
- **mls_source_last_updated** (string)
- **on_market_timestamp** (string)
- **status** (string)
- **public_remarks** (string), Full property description
- **photos** (array), Array of photo URLs (up to 40)
- **listing_url** (string), Canonical Stessa detail URL
- **redfin_url** (string)
- **zillow_zpid** (string)
- **main_image_url** (string)


### Quick start

1. Click **Try for free**.
2. Leave the input at defaults, or paste the recommended input from lower on this page.
3. Click **Start**. The default run finishes in under a minute with a small sample.
4. Open the **Dataset** tab to inspect the shape.
5. Once you like the output, click **Schedule** and pick a cadence. Daily is typical.

### How a run works

1. **Scan**, walks stessa.com from the seed URL, paginating through list pages.
2. **Fetch**, visits each detail page and extracts the fields listed above.
3. **Diff**, compares each record against the snapshot in your named key-value store, labelling it `new`, `changed` or `unchanged`.
4. **Persist**, the new snapshot is saved for the next run.

#### What is a delta feed and why does it save money?

A delta feed only pays for what changed since your last run. Instead of re-scraping every record each night, this scraper compares against your stored snapshot and only fully processes rows that added, changed or first appeared. Enable `onlyChanged` to skip unchanged rows entirely (they are billed at a discounted rate).

#### What differentiates this scraper?

Roofstock merged into Stessa in 2025. This scraper is the only way to programmatically access Roofstock's proprietary Neighborhood Score (1-5) and Roofstock rent estimate (with low/high/confidence range) alongside standard MLS listing data, HOA cost, listing agent contact (respecting the source's display flags), full photo array of up to 40 images, Zillow ZPID and Redfin URL cross-links. No MLS aggregator publishes the Roofstock proprietary fields; no Stessa-native scraper exists.

### Common use cases

- Weekly buy-box screening across 40+ US markets, filtered by yield, Neighborhood Score, or price band
- Price-change and cap-rate drift tracking on a daily schedule
- New-listing alerts filtered by CBSA metro or rent estimate
- Listing-agent prospecting lists (with display-flag compliance)
- Photo enrichment for underwriting and comparable analysis


### How to interpret the fields

#### What does Neighborhood Score mean?

Stessa's proprietary 1-5 rating for the block group the property sits in. Weights demographics, school quality, rental demand and crime data. A 4 or 5 typically means investor-friendly (stable rents, high tenant retention). A 1 or 2 flags markets with volatile occupancy or elevated eviction risk. Use as a first-pass buy-box filter; not a fair-lending signal.

#### What does Gross yield mean?

Annual rent divided by purchase price, expressed as a decimal (0.077 means 7.7%). Ignores expenses and financing. A quick "how much rent does this asset produce" ratio. Compare across markets before diving into full pro-forma.

#### What does Cap rate mean?

Net operating income divided by purchase price. Accounts for taxes, insurance, HOA, PM, vacancy and repairs but not mortgage. Investors typically want 5%+ cap rate on Stessa-class SFR turnkey; sub-3% often signals overpriced-for-cashflow (or high-appreciation market).

#### What does Cash on cash mean?

Year-1 cash flow divided by cash invested (down payment + closing). Includes mortgage. A leveraged buyer's most-cited metric. Negative means the property loses money year one; positive means it pays for itself.

#### What does Rent estimate (low / high / confidence) mean?

Stessa's automated valuation model for monthly rent, with a range and a confidence level (Low, Medium, High). Low-confidence estimates typically mean sparse comps for the block group. Verify against the sale/rent comps tables when confidence is Low.



### How this compares to alternative scrapers

#### Stessa scraper vs Zillow scraper

Every record from this actor includes `zillow_zpid`. Pass those ZPIDs into a Zillow scraper to cross-check Stessa's rent estimate against Zillow's Rent Zestimate. Zillow does not publish Neighborhood Score or cap rate; Stessa does not publish Zestimate. Use both together for triangulated valuation.

#### Stessa scraper vs Redfin scraper

Every record from this actor includes `redfin_url`. Redfin's public property pages publish Redfin Estimate (AVM), price history, walk/transit/bike scores and full school breakdowns. Stessa does not. Feed the redfin_url list into a Redfin scraper for those enrichments.

#### Stessa scraper vs the MLS export

Direct MLS access requires broker credentials, quotas and NDAs. This scraper is public-marketplace-only, which means no MLS agreement is required, but also that some fields (like historical sold archives) are not exposed. For volume MLS access, IDX or RETS feeds via a broker are the alternative.



### How much does it cost to scrape stessa.com?

Pay-per-event pricing:

- **Actor start**: $0.00500 per GB of memory, one event per run.
- **Property scraped**: $0.00800 per record delivered to the dataset.
- **Property checked, unchanged**: $0.00150 per row when `onlyChanged` is on and the row was unchanged. Roughly 19% the cost of a full record.

There are no per-request charges and no page-count charges. Costs come only from records you actually receive. A scheduled monitor in delta mode typically costs cents per day; a first-time full crawl is the expensive case that the delta model is designed to avoid on subsequent runs.

#### Concrete cost examples

Rule-of-thumb cost per run (assumes primary event only, no delta discount):

- **100 records** = about $0.80 in records + one actor-start fee
- **500 records** = about $4.00
- **1,000 records** = about $8.00
- **10,000 records** = about $80.00

With delta mode + `onlyChanged: true`, a daily monitor of 1,000 properties where 5% change costs roughly **$1.83 per run** (50 full charges + 950 delta-priced skips).

### Common questions

#### Does this scraper need a Stessa account?

No. All listing data is scraped from public marketplace pages at stessa.com/investment-properties. You do not need credentials.

#### Is scraping Stessa legal?

Reading public marketplace pages that are not gated behind login and respecting robots.txt is broadly permitted. Stessa's robots.txt allows /investment-properties. Listing agent contact fields are additionally gated by each MLS source's per-field display flags, which this scraper respects. You are responsible for downstream compliance (TCPA, CAN-SPAM, state privacy laws) when using agent contact data.

#### Why does the actor say 'Roofstock' if this is a Stessa scraper?

Roofstock acquired / merged into Stessa in 2025 and the marketplace is now served under stessa.com. Same inventory, same fields, new URL. The actor name is Stessa-first because that is where the data lives today.

#### What is the Stessa marketplace inventory count?

Over 1 million listings across 40+ US metros as of last snapshot. Includes active for-sale properties across single-family, condo, townhouse, and multi-family.

#### What is a Neighborhood Score?

Stessa (formerly Roofstock) proprietary 1-5 rating for the block group the property sits in. Weights demographics, school quality, and rental demand. Preserved in the merger. Only on Stessa, no other MLS aggregator returns it.

#### What is the rent estimate?

A Stessa-computed monthly rent for the property, with a low/high range and a confidence level (Low, Medium, High). Field name in the API response is roofstockRent (legacy). Separate from seller-provided rent (which is disclosed if the current owner has a tenant).

#### Can I use this data for underwriting?

Yes. Enable includeInvestmentCalc to also get Stessa's full year-1 pro-forma (mortgage, insurance, tax, HOA, PM, leasing, vacancy, repairs, NOI, net cash flow) per property. Combined with base fields you have a complete underwriting-input row.

#### How does this compare to a Zillow or Redfin scraper?

Every record includes zillow_zpid and redfin_url. Use those as join keys to feed into the companion Zillow or Redfin enrichment scrapers for cross-source AVM comparison. Only Stessa returns Neighborhood Score and Roofstock rent estimate.

#### Can I filter by CBSA metro area?

Yes. The scraper returns the CBSA code per property. Filter downstream to isolate a metro. Stessa covers 40+ US markets.

#### Is listing agent contact info always included?

Only when the MLS source's display flags permit. Each MLS has different rules on whether agent name, phone, email, license can be republished. This scraper respects those flags and blanks out fields the source forbids.

#### How much does it cost to scrape 1000 Stessa properties?

See the cost examples section below. Base scrape (no comps, no calc, no photos) is about $8 per 1,000 records. With includeInvestmentCalc enabled it is closer to $20 per 1,000.

#### Does this scraper need an account with stessa.com?

No. The scraper reads only public pages. You do not need credentials.

#### Why do I need to enable Apify Proxy?

Most sites at scale trigger rate limits or challenges against datacenter IPs. Apify Proxy (residential recommended) handles this. The default input keeps `useApifyProxy: true`.

#### Why does the state store need a name?

Delta mode compares each run against the previous snapshot. Apify wipes the default (unnamed) key-value store on every run, which would erase the snapshot. A NAMED store (`stateStoreName: "stessa-scraper-state"`) persists across runs.

#### Can I run in delta mode without paying for unchanged rows?

Yes. Set `onlyChanged: true`. Unchanged rows are billed at the discounted `record-checked` rate; changed rows at the full `property` rate. New rows are always full price.

### Recommended daily-schedule input

```json
{
  "maxItems": 3,
  "onlyChanged": false,
  "useApifyProxy": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
````

For a full-catalogue first run, raise `maxItems` to `0` (no cap) and leave `onlyChanged: false`. For daily monitoring, set `onlyChanged: true` and keep `maxItems` at `0`.

### Turn it into a live alert with Schedules and Webhooks

Combine this scraper with an Apify **Schedule** and a **Webhook** to get "new listing in my buy box" alerts without writing code.

1. Configure the input with your buy-box filters (yield floor, price ceiling, markets, etc).
2. Create a Schedule (Actor page, right rail, Schedule) at your cadence. Daily 06:00 UTC is typical.
3. Add a Webhook (Integrations tab on this actor):
   - Event: `ACTOR.RUN.SUCCEEDED`
   - URL: your Slack / Discord / Zapier / n8n endpoint
   - Payload template: pass `resource.defaultDatasetId` so the receiver can pull only the delta rows

For Slack, wrap the payload in a `text` field and push through an [incoming webhook](https://api.slack.com/messaging/webhooks). For Zapier or n8n, use the "Webhook by Apify" trigger and map dataset fields to your downstream tool of choice.

Pair this with `onlyChanged: true` and the actor becomes a pure alert feed. Delta-priced rows keep the bill flat on quiet days.

### Related scrapers from this publisher

See other actors under [growth-installations](https://apify.com/growth-installations) for related consumer-intelligence scrapers.

### Video walkthrough

Video walkthrough placeholder. Add a YouTube URL to `readme.video_url` in the yaml to auto-embed once one is published.

### Notes and limits

- Keep concurrency modest to stay polite to stessa.com. Default is 5.
- Field schemas can shift when the target site restyles. If a field starts returning `null`, report it and it will be patched.
- The default input is engineered to complete in under 5 minutes so the platform's daily automated test passes and the actor does not get flagged for maintenance.

### Legal

This actor reads only public marketplace pages, with no circumvention of access controls. Stessa's robots.txt permits crawling of /investment-properties. Listing agent contact fields are gated by each MLS source's display flags and are blanked when the source forbids republication. You are responsible for compliance with applicable law, MLS terms of service, and any downstream do-not-contact regulations (TCPA, CAN-SPAM, state privacy laws) when using agent contact data.

# Actor input Schema

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

Stop after this many records. 0 = no cap (crawl all pages).

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

Hard cap on pagination clicks (each page is 24 listings). 0 = up to 50.

## `includeOffMarket` (type: `boolean`):

Toggle Stessa's Off-Market Beta feed. Higher WTP for distressed-asset investors.

## `includeSaleComps` (type: `boolean`):

Renders each property's detail page in a headless browser and extracts the 'Comparable sale properties' table (nearby active listings). Written to the `comps` named dataset keyed by property\_id.

## `filters` (type: `object`):

Filter records at scrape time. All fields optional; combine as an AND.

## `includeRentComps` (type: `boolean`):

Renders each property's detail page in a headless browser and extracts the 'Comparable rent properties' table (nearby rentals). Written to the `comps` named dataset keyed by property\_id.

## `includeInvestmentCalc` (type: `boolean`):

Opens the 'Edit assumptions' modal in a headless browser and captures Stessa's full year-1 pro-forma: purchase price, down payment, loan rate, monthly rent, vacancy %, property tax, PM %, leasing %, HOA, insurance, repairs reserve, mortgage yearly, annual income, operating expenses, NOI, net cash flow. Written to the `comps` dataset alongside comps (if requested).

## `enrichConcurrency` (type: `integer`):

Number of parallel Playwright browsers for the comps enrichment pass.

## `downloadPhotos` (type: `boolean`):

When true, fetches up to 40 photos per property and stores them under photoStoreName. Adds cost per record.

## `photoStoreName` (type: `string`):

Named store where downloaded JPEGs live.

## `onlyChanged` (type: `boolean`):

Compare each row against the last snapshot; skip unchanged. Unchanged rows billed at discounted rate.

## `stateStoreName` (type: `string`):

Named key-value store used to persist snapshots between runs.

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

Residential proxy REQUIRED. This site blocks datacenter IPs — the actor will exit with an error if RESIDENTIAL is not selected in the proxy groups.

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

Cap on parallel Cheerio requests for detail pages.

## `listConcurrency` (type: `integer`):

Number of Playwright browsers running LIST pagination in parallel. Almost always 1.

## `maxRequestRetries` (type: `integer`):

Retry a failed request this many times before giving up. Raise for flaky proxies, lower for fast-fail behaviour.

## `requestHandlerTimeoutSecs` (type: `integer`):

Hard timeout per request handler execution. Default 180s to accommodate the LIST phase walking multiple pages.

## `sessionPoolMaxSize` (type: `integer`):

Number of long-lived sessions with cookies. Larger pool = more IP rotation but slower cookie warmup.

## `sessionMaxUsageCount` (type: `integer`):

Sessions get retired after this many requests, forcing a fresh IP and cookies.

## `sameDomainDelaySecs` (type: `integer`):

Pause between consecutive requests to stessa.com. Only enable if you see 429s. 0 = no delay.

## `maxErrorRateAbort` (type: `number`):

If more than this fraction (0.0-1.0) of DETAIL requests fail, abort before running the ENRICH phase to avoid billing on a broken run. 0 = never abort.

## Actor input object example

```json
{
  "maxItems": 3,
  "maxPages": 0,
  "includeOffMarket": false,
  "includeSaleComps": false,
  "filters": {
    "minYield": 0.06,
    "maxPrice": 500000,
    "minBeds": 2,
    "minNeighborhoodScore": 3
  },
  "includeRentComps": false,
  "includeInvestmentCalc": false,
  "enrichConcurrency": 2,
  "downloadPhotos": false,
  "photoStoreName": "stessa-photos",
  "onlyChanged": false,
  "stateStoreName": "roofstock-stessa-state",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 5,
  "listConcurrency": 1,
  "maxRequestRetries": 3,
  "requestHandlerTimeoutSecs": 180,
  "sessionPoolMaxSize": 40,
  "sessionMaxUsageCount": 20,
  "sameDomainDelaySecs": 0,
  "maxErrorRateAbort": 0
}
```

# 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 = {
    "maxItems": 3,
    "filters": {
        "minYield": 0.06,
        "maxPrice": 500000,
        "minBeds": 2,
        "minNeighborhoodScore": 3
    },
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("growth-installations/stessa-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 = {
    "maxItems": 3,
    "filters": {
        "minYield": 0.06,
        "maxPrice": 500000,
        "minBeds": 2,
        "minNeighborhoodScore": 3,
    },
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("growth-installations/stessa-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "maxItems": 3,
  "filters": {
    "minYield": 0.06,
    "maxPrice": 500000,
    "minBeds": 2,
    "minNeighborhoodScore": 3
  },
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call growth-installations/stessa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=growth-installations/stessa-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Stessa Investment Properties Scraper - Rental Data",
        "description": "Scrape Stessa's investment-property marketplace (formerly Roofstock) for 37k+ turnkey single-family listings. Extract Roofstock Neighborhood Score, rent estimate with low/high/confidence, HOA, MLS number, listing-agent contact, photo array and CBSA metro code. Daily delta feed.",
        "version": "0.0",
        "x-build-id": "2LW6gbFjPwnOcA0S1"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/growth-installations~stessa-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-growth-installations-stessa-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/growth-installations~stessa-scraper/runs": {
            "post": {
                "operationId": "runs-sync-growth-installations-stessa-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/growth-installations~stessa-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-growth-installations-stessa-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "maxItems": {
                        "title": "Maximum items",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Stop after this many records. 0 = no cap (crawl all pages).",
                        "default": 3
                    },
                    "maxPages": {
                        "title": "Maximum list pages",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Hard cap on pagination clicks (each page is 24 listings). 0 = up to 50.",
                        "default": 0
                    },
                    "includeOffMarket": {
                        "title": "Include Off-Market Beta listings",
                        "type": "boolean",
                        "description": "Toggle Stessa's Off-Market Beta feed. Higher WTP for distressed-asset investors.",
                        "default": false
                    },
                    "includeSaleComps": {
                        "title": "Enrich with sale comps (Playwright, +cost)",
                        "type": "boolean",
                        "description": "Renders each property's detail page in a headless browser and extracts the 'Comparable sale properties' table (nearby active listings). Written to the `comps` named dataset keyed by property_id.",
                        "default": false
                    },
                    "filters": {
                        "title": "Buy-box filters",
                        "type": "object",
                        "description": "Filter records at scrape time. All fields optional; combine as an AND.",
                        "default": {}
                    },
                    "includeRentComps": {
                        "title": "Enrich with rent comps (Playwright, +cost)",
                        "type": "boolean",
                        "description": "Renders each property's detail page in a headless browser and extracts the 'Comparable rent properties' table (nearby rentals). Written to the `comps` named dataset keyed by property_id.",
                        "default": false
                    },
                    "includeInvestmentCalc": {
                        "title": "Enrich with year-1 pro-forma calculator (Playwright, +cost)",
                        "type": "boolean",
                        "description": "Opens the 'Edit assumptions' modal in a headless browser and captures Stessa's full year-1 pro-forma: purchase price, down payment, loan rate, monthly rent, vacancy %, property tax, PM %, leasing %, HOA, insurance, repairs reserve, mortgage yearly, annual income, operating expenses, NOI, net cash flow. Written to the `comps` dataset alongside comps (if requested).",
                        "default": false
                    },
                    "enrichConcurrency": {
                        "title": "Max concurrency (ENRICH phase)",
                        "minimum": 1,
                        "maximum": 4,
                        "type": "integer",
                        "description": "Number of parallel Playwright browsers for the comps enrichment pass.",
                        "default": 2
                    },
                    "downloadPhotos": {
                        "title": "Download property photos to key-value store",
                        "type": "boolean",
                        "description": "When true, fetches up to 40 photos per property and stores them under photoStoreName. Adds cost per record.",
                        "default": false
                    },
                    "photoStoreName": {
                        "title": "Photo key-value store name",
                        "type": "string",
                        "description": "Named store where downloaded JPEGs live.",
                        "default": "stessa-photos"
                    },
                    "onlyChanged": {
                        "title": "Delta mode (only push changed rows)",
                        "type": "boolean",
                        "description": "Compare each row against the last snapshot; skip unchanged. Unchanged rows billed at discounted rate.",
                        "default": false
                    },
                    "stateStoreName": {
                        "title": "State store name",
                        "type": "string",
                        "description": "Named key-value store used to persist snapshots between runs.",
                        "default": "roofstock-stessa-state"
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Residential proxy REQUIRED. This site blocks datacenter IPs — the actor will exit with an error if RESIDENTIAL is not selected in the proxy groups.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ]
                        }
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency (DETAIL phase)",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Cap on parallel Cheerio requests for detail pages.",
                        "default": 5
                    },
                    "listConcurrency": {
                        "title": "Max concurrency (LIST phase)",
                        "minimum": 1,
                        "maximum": 4,
                        "type": "integer",
                        "description": "Number of Playwright browsers running LIST pagination in parallel. Almost always 1.",
                        "default": 1
                    },
                    "maxRequestRetries": {
                        "title": "Max request retries",
                        "minimum": 0,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Retry a failed request this many times before giving up. Raise for flaky proxies, lower for fast-fail behaviour.",
                        "default": 3
                    },
                    "requestHandlerTimeoutSecs": {
                        "title": "Per-request timeout (seconds)",
                        "minimum": 10,
                        "maximum": 600,
                        "type": "integer",
                        "description": "Hard timeout per request handler execution. Default 180s to accommodate the LIST phase walking multiple pages.",
                        "default": 180
                    },
                    "sessionPoolMaxSize": {
                        "title": "Session pool size",
                        "minimum": 4,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Number of long-lived sessions with cookies. Larger pool = more IP rotation but slower cookie warmup.",
                        "default": 40
                    },
                    "sessionMaxUsageCount": {
                        "title": "Requests per session before rotation",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Sessions get retired after this many requests, forcing a fresh IP and cookies.",
                        "default": 20
                    },
                    "sameDomainDelaySecs": {
                        "title": "Delay between requests to same domain (seconds)",
                        "minimum": 0,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Pause between consecutive requests to stessa.com. Only enable if you see 429s. 0 = no delay.",
                        "default": 0
                    },
                    "maxErrorRateAbort": {
                        "title": "Abort if DETAIL error rate exceeds",
                        "minimum": 0,
                        "maximum": 1,
                        "type": "number",
                        "description": "If more than this fraction (0.0-1.0) of DETAIL requests fail, abort before running the ENRICH phase to avoid billing on a broken run. 0 = never abort.",
                        "default": 0
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
