# STR vs LTR Market Lens — Airbnb, Vrbo & Apartments.com (`sian.agency/str-investor-market-lens`) Actor

STR vs LTR market lens for property investors. Pulls short-term (Airbnb, Vrbo) and long-term (Apartments.com) rates side-by-side, normalizes to monthly equivalents and break-even occupancy — the spreadsheet you'd build by hand, in one run.

- **URL**: https://apify.com/sian.agency/str-investor-market-lens.md
- **Developed by:** [SIÁN OÜ](https://apify.com/sian.agency) (community)
- **Categories:** Real estate, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 market comparison rows

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 a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.md):

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

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.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/platform/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

## STR vs LTR Market Lens — Airbnb, Vrbo & Apartments.com 🚀

[![SIÁN Agency Store](https://img.shields.io/badge/Store-SI%C3%81N%20Agency-1AE392)](https://apify.com/sian.agency?fpr=sian) [![SIÁN-Airbnb Scraper](https://img.shields.io/badge/SI%C3%81N-Airbnb%20Scraper-FF5A5F)](https://apify.com/sian.agency/airbnb-property-scraper?fpr=sian) [![SIÁN-Vrbo Scraper](https://img.shields.io/badge/SI%C3%81N-Vrbo%20Scraper-1AE392)](https://apify.com/sian.agency/vrbo-property-scraper?fpr=sian) [![SIÁN-Apartments.com Scraper](https://img.shields.io/badge/SI%C3%81N-Apartments.com%20Scraper-1AE392)](https://apify.com/sian.agency/apartments-com-property-scraper?fpr=sian)

#### 🎉 NEW: The first cross-channel STR-vs-LTR decision tool on the Apify Store — pulls Airbnb, Vrbo, and Apartments.com in one run and normalizes every row to a single `monthly_equivalent_rate_usd` for side-by-side acquisition due diligence.
##### Built for real-estate investors, STR operators, and acquisition analysts who need to answer "Should I list this property short-term or long-term?" before they buy — not after.

### 📋 Overview

**Should this property earn more as an Airbnb, a Vrbo, or a 12-month rental?** — that's the acquisition-phase question this actor answers in a single run. Most rental scrapers serve operators *inside* one channel. This one serves the buyer-side decision *across* channels.

Out of ~25,000 actors on the Apify Store, **zero** ship a cross-platform STR-vs-LTR comparison. The off-Apify analogs (AirDNA, KeyData, Rabbu, Mashvisor) start at $20–$300/month subscriptions, and Mashvisor — the closest comparable — doesn't even cover Vrbo. At $0.012 per `marketComparisonRow` on the BRONZE tier, you undercut the cheapest of them on pay-per-use AND add the Vrbo channel they miss.

**Why investors and analysts choose this actor:**
- ✅ **STR-vs-LTR Acquisition Lens**: The only Apify actor that fuses short-term and long-term rates side-by-side — built for the moment *before* you buy, not for ongoing ops.
- ⚡ **3 Platforms, 1 API Call**: Pull Airbnb, Vrbo, and Apartments.com in parallel from a single input. Cross-channel fan-out finishes in ~45 seconds for 50 rows/channel.
- 🎯 **Normalized `monthly_equivalent_rate_usd`**: Every row carries the cross-channel comparison primitive — `nightly × 30.4 × occupancy` for STR, native monthly rent for LTR. Sort, compare, decide.
- 💰 **Pay-Per-Row, Not Per-Month**: $0.012 per market-comparison row vs Mashvisor's $35/month floor. Casual investors stop paying when they stop researching.
- 💎 **Break-Even Occupancy %**: The single most useful number for an STR acquisition — "this Airbnb needs 75% occupancy to beat the LTR baseline." Computed on every STR row.
- ✨ **NEW**: `cross_listing_candidates[]` — coordinate-proximity matching (50 m + bedroom-count + property-type) surfaces "same property listed on both Airbnb and Vrbo" without an exact address join.

### ✨ Features

- 🏘 **3-Channel Cross-Fusion**: Airbnb (STR), Vrbo (STR), and Apartments.com (LTR) merged into one schema, one dataset, one report.
- 💰 **Monthly-Equivalent Rate Normalization**: Convert any nightly rate to a comparable monthly figure using a configurable occupancy assumption (default 0.70).
- 🎯 **Break-Even Occupancy %**: For every STR row, the minimum bookings needed to match the run's median LTR monthly rent — the decision-critical number.
- 🔗 **Cross-Listing Candidate Detection**: Identifies likely cross-listed properties using 50 m coordinate proximity + bedroom + property-type match.
- 📊 **HTML Market Report**: A self-contained, shareable report (`report.html`) with channel medians, break-even distribution, and cross-listing match counts — written to the key-value store on every run.
- 📅 **Optional Live Calendar Enrichment**: Toggle `enrichSTRCalendar` to pull forward-12-month Airbnb and Vrbo availability per listing and replace the static occupancy baseline with real bookings data.
- 🔍 **3 Search Modes**: Location string (`Miami, FL`), coordinates + radius (km), or US ZIP code — pick whichever matches your sourcing workflow.
- 🔢 **Pay-Per-Event Pricing**: Charged only for the rows you receive — and only for the headline event when 2+ channels return data. Single-channel runs pay commodity rates.
- 🎁 **Free Tier**: 25 rows per channel — enough to scope a market end-to-end before committing.
- 📈 **Warehouse-Ready Output**: Standard JSON-line dataset → Snowflake / BigQuery / Postgres via Apify's built-in integrations.

### 🎬 Quick Start

Set a market, pick your channels, run. Every row in the dataset carries a `monthly_equivalent_rate_usd` you can sort and compare across Airbnb, Vrbo, and Apartments.com directly.

```bash
curl -X POST "https://api.apify.com/v2/acts/sian.agency~str-investor-market-lens/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchMode": "location", "location": "Miami, FL"}'
````

### 🚀 Getting Started (3 Simple Steps)

#### Step 1: Pick a market

Open the actor in [Apify Console](https://console.apify.com/actors/) and set `searchMode` to `location` with a target like `Miami, FL`, or `coordinates` for a precise neighbourhood radius, or `zip` for a US postal code.

#### Step 2: Confirm channels

Leave `channels` at the default `["airbnb", "vrbo", "apartments"]` — the headline `marketComparisonRow` event only fires when 2+ channels return data, so cross-channel runs unlock the full value.

#### Step 3: Run and open `report.html`

Click **Start**. Dataset rows arrive in real time; the HTML market report lands in the key-value store on completion — open it in any browser to see channel medians, break-even distribution, and cross-listing hits.

**That's it! In under 60 seconds for a standard market, you'll have:**

- A normalized `monthly_equivalent_rate_usd` on every row across all three channels
- A `break_even_occupancy_pct` for every STR row — the minimum bookings to match LTR
- A `cross_listing_candidates[]` array flagging likely Airbnb↔Vrbo duplicates
- A shareable HTML market report with median rates per channel
- A warehouse-ready dataset that plumbs straight into Snowflake, BigQuery, or Google Sheets

### 📥 Input Configuration

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `searchMode` | enum | Yes | `location`, `coordinates`, or `zip` |
| `location` | string | When `searchMode=location` | Free-text destination — `Miami, FL`, `Brooklyn, NY` |
| `latitude` | string | When `searchMode=coordinates` | Decimal latitude (-90 to 90) |
| `longitude` | string | When `searchMode=coordinates` | Decimal longitude (-180 to 180) |
| `radiusKm` | integer | No (default 10) | Search radius in km for coordinate mode (1-100) |
| `zip` | string | When `searchMode=zip` | 5-digit US ZIP code |
| `channels` | array | No (default all 3) | Subset of `["airbnb", "vrbo", "apartments"]` |
| `maxResultsPerChannel` | integer | No (default 50) | Per-channel row cap (FREE tier locked to 25) |
| `occupancyBaseline` | string | No (default `"0.70"`) | STR occupancy assumption for monthly-equivalent calc |
| `enrichSTRCalendar` | boolean | No (default `false`) | Pull live Airbnb / Vrbo availability calendars per listing |
| `computeCrossListingCandidates` | boolean | No (default `true`) | Compute `cross_listing_candidates[]` per row (free) |
| `checkIn` | string | No | ISO `YYYY-MM-DD` — drives STR per-night prices for the window |
| `checkOut` | string | No | ISO `YYYY-MM-DD` — must be paired with `checkIn` |

**Example — single market sweep:**

```json
{
  "searchMode": "location",
  "location": "Miami, FL",
  "channels": ["airbnb", "vrbo", "apartments"],
  "maxResultsPerChannel": 50,
  "occupancyBaseline": "0.70"
}
```

**Example — coordinate-radius scan with live calendar:**

```json
{
  "searchMode": "coordinates",
  "latitude": "25.77",
  "longitude": "-80.19",
  "radiusKm": 5,
  "channels": ["airbnb", "vrbo", "apartments"],
  "enrichSTRCalendar": true,
  "computeCrossListingCandidates": true
}
```

**Example — US ZIP scan:**

```json
{
  "searchMode": "zip",
  "zip": "33134",
  "channels": ["airbnb", "vrbo", "apartments"]
}
```

### 📤 Output

Results are saved to the Apify dataset with **30+ fields per row**, including the cross-channel comparison primitives that drive the acquisition decision:

| Field | Type | Description |
|-------|------|-------------|
| `source_platform` | string | `airbnb`, `vrbo`, or `apartments` |
| `listing_id` | string | Native ID (Airbnb base64, Vrbo numeric, Apartments listingKey) |
| `url` | string | Canonical listing URL |
| `name` | string | Listing title |
| `address_line` | string | Street address — Apartments.com only |
| `city`, `state`, `postal_code` | string | Location signals |
| `latitude`, `longitude` | number | Geographic anchor (when available per platform) |
| `bedrooms`, `bedrooms_max`, `bathrooms`, `property_type` | mixed | Unit profile |
| `rental_type` | string | `STR` for Airbnb/Vrbo, `LTR` for Apartments |
| `nightly_rate_usd` | number | Per night for STR; `monthly_rent / 30.4` for LTR |
| `monthly_rent_usd` | number | Native for LTR; null for STR |
| **`monthly_equivalent_rate_usd`** | number | **The cross-channel comparison primitive** |
| `occupancy_decimal_used` | number | Occupancy used in the calc |
| `occupancy_source` | string | `baseline`, `live_calendar`, or `ltr_default` |
| `break_even_occupancy_pct` | number | STR only — minimum bookings to match LTR median |
| `cross_listing_candidates` | array | Peer-platform rows within 50 m + same bedrooms |
| `is_market_comparison_row` | boolean | True when the row was charged the headline event |
| `channels_in_run` | array | Which channels produced data in this run |
| `review_count`, `rating`, `photos_count`, `primary_image_url` | mixed | Quality signals |
| `calendar_summary` | object | Populated when calendar enrichment is enabled |
| `scraped_at` | string | ISO timestamp |

**Worked example — Miami 1BR, 2026-05-21:**

```json
[
  {
    "source_platform": "apartments",
    "listing_id": "218qyk8",
    "city": "Miami",
    "state": "FL",
    "bedrooms": 1,
    "rental_type": "LTR",
    "monthly_rent_usd": 3210,
    "monthly_equivalent_rate_usd": 3210,
    "nightly_rate_usd": 105.59,
    "occupancy_source": "ltr_default",
    "is_market_comparison_row": true
  },
  {
    "source_platform": "airbnb",
    "listing_id": "53568731",
    "city": "Miami",
    "bedrooms": 1,
    "rental_type": "STR",
    "nightly_rate_usd": 140.40,
    "occupancy_decimal_used": 0.70,
    "occupancy_source": "baseline",
    "monthly_equivalent_rate_usd": 2987.71,
    "break_even_occupancy_pct": 75.2,
    "is_market_comparison_row": true
  },
  {
    "source_platform": "vrbo",
    "listing_id": "117961754",
    "city": "Miami",
    "bedrooms": 1,
    "rental_type": "STR",
    "nightly_rate_usd": 58.00,
    "occupancy_decimal_used": 0.70,
    "occupancy_source": "baseline",
    "monthly_equivalent_rate_usd": 1235.04,
    "break_even_occupancy_pct": 182.4,
    "is_market_comparison_row": true
  }
]
```

**Reading the row above:** the Apartments.com baseline pays $3,210/month. The Airbnb 1BR at $140.40/night needs 75% occupancy to match it — achievable in South Beach. The Vrbo cabin at $58/night would need 182% occupancy (impossible) — it should stay LTR. **One run, three channels, one decision.**

The actor also writes a self-contained **HTML market report** to the key-value store at `report.html` — channel medians, break-even distribution, cross-listing hit count, ready to share.

### 💼 Use Cases & Examples

#### 1. Acquisition Due Diligence for STR Investors

**Real-estate investors evaluating a property before purchase**

**Input:** Target zip or coordinates around a candidate property
**Output:** Median Airbnb / Vrbo / LTR rates for the neighbourhood + the unit's break-even occupancy
**Use:** Decide whether to underwrite the unit as a short-term rental, long-term rental, or pass on the deal entirely — before committing capital.

#### 2. Market-Entry Feasibility for STR Operators

**STR managers expanding into new cities**

**Input:** 5 candidate markets, run sequentially or in parallel
**Output:** Median `monthly_equivalent_rate_usd` per channel per market
**Use:** Pick the city with the widest STR-over-LTR delta — that's where your operational margin is largest.

#### 3. Channel-Mix Optimization for Mixed Portfolios

**Property managers running blended STR + LTR books**

**Input:** Coordinates around each existing unit
**Output:** Side-by-side neighbourhood rates for both channels
**Use:** Flag mis-channelled units — long-term leases earning what short-term could be earning, or vice versa — and re-platform them.

#### 4. Listing-Price Benchmarking for Independent Landlords

**Solo landlords with 1-5 units**

**Input:** Single neighbourhood, default channels
**Output:** Median market rates across STR and LTR
**Use:** Verify your current lease rate is at market — or discover you've been under-pricing for years.

#### 5. Investor-Report Automation for Real-Estate Funds

**Acquisition analysts at REITs and PE funds**

**Input:** Scripted batch of target markets, daily or weekly
**Output:** Warehouse-ready dataset piped into Snowflake / BigQuery / Redshift
**Use:** `marketComparisonRow` is the canonical investment-grade row — feeds straight into portfolio underwriting models.

#### 6. AirDNA / Mashvisor Alternative for Casual Researchers

**Solo investors not ready to subscribe to a $35/month tool**

**Input:** One market, one run
**Output:** All the rate intelligence Mashvisor charges $35/month for + Vrbo data they don't ship
**Use:** Pay $0.50 once for a market scan vs $420/year for a subscription you'll use once.

#### 7. Cross-Listing Discovery for Compliance Audits

**City officials and HOAs investigating short-term rental compliance**

**Input:** Specific neighbourhood coordinates
**Output:** `cross_listing_candidates[]` — likely same-property cross-listings between Airbnb and Vrbo
**Use:** Identify operators running unpermitted STR units across multiple platforms.

### 🔗 Integration Examples

#### JavaScript / Node.js

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });

const run = await client.actor('sian.agency/str-investor-market-lens').call({
  searchMode: 'location',
  location: 'Miami, FL',
  channels: ['airbnb', 'vrbo', 'apartments'],
  maxResultsPerChannel: 50
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const headline = items.filter(r => r.is_market_comparison_row);
console.log(`${headline.length} comparison rows across ${[...new Set(headline.map(r => r.source_platform))].length} channels`);
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')

run = client.actor('sian.agency/str-investor-market-lens').call(
    run_input={
        'searchMode': 'location',
        'location': 'Miami, FL',
        'channels': ['airbnb', 'vrbo', 'apartments'],
        'enrichSTRCalendar': False,
    }
)

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    if item.get('rental_type') == 'STR' and item.get('break_even_occupancy_pct'):
        print(item['source_platform'], item['city'], item['break_even_occupancy_pct'])
```

#### cURL

```bash
curl -X POST "https://api.apify.com/v2/acts/sian.agency~str-investor-market-lens/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchMode": "location", "location": "Miami, FL", "channels": ["airbnb", "vrbo", "apartments"]}'
```

#### jq — Sort STR rows by margin over LTR baseline

```bash
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>" \
  | jq '[.[] | select(.rental_type=="STR" and .break_even_occupancy_pct)] | sort_by(.break_even_occupancy_pct) | .[0:10]'
```

The rows with the lowest break-even occupancy are the strongest STR acquisitions — they need the fewest booked nights to clear the LTR floor.

#### Automation Workflows (N8N / Zapier / Make)

1. **Trigger**: Schedule (daily / weekly) or webhook on a new sourcing record
2. **HTTP Request**: Call the actor with the target market
3. **Process**: Filter rows where `is_market_comparison_row === true`
4. **Action**: Write to Google Sheets / Snowflake / Notion / Slack

### 📊 Performance & Pricing

#### FREE Tier (Scope a market for free)

- **25 rows per channel** per run — full schema, same normalization math
- Calendar enrichment disabled
- No credit card required
- Perfect for sizing up a single market before committing budget

#### PAID Tier (Production-ready acquisition workflow)

- **Up to 500 rows per channel** per run
- Live STR calendar enrichment unlocked
- Pay-per-event — you're charged only for rows you receive, and only for the headline event when 2+ channels return data
- Tiered discounts unlock automatically — BRONZE → DIAMOND, no manual upgrades

| Event | What triggers it | BRONZE | GOLD | DIAMOND |
|-------|------------------|--------|------|---------|
| `apify-actor-start` | One per run, post-validation | $0.005 | $0.005 | $0.005 |
| `marketComparisonRow` | **Headline** — row with `monthly_equivalent_rate_usd` when ≥2 channels active | $0.012 | $0.008 | $0.005 |
| `airbnbListingExtracted` | Per Airbnb row | $0.004 | $0.003 | $0.002 |
| `vrboListingExtracted` | Per Vrbo row | $0.004 | $0.003 | $0.002 |
| `apartmentsListingExtracted` | Per Apartments.com row | $0.004 | $0.003 | $0.002 |
| `strCalendarEnriched` | Per STR listing with live calendar fetch | $0.015 | $0.010 | $0.006 |

💰 **Best price on the market** — Mashvisor starts at $35/month for a similar comparison (and skips Vrbo). At $0.012 per `marketComparisonRow`, $20 of credit buys ~500 cross-channel comparison rows.

Single-channel runs (only `airbnb`, only `vrbo`, or only `apartments` selected) **do not trigger** the headline event — you pay commodity rates only.

🔗 [View current pricing](https://apify.com/sian.agency/str-investor-market-lens?fpr=sian)

### ❓ Frequently Asked Questions

**Q: Why don't Airbnb and Vrbo rows in my dataset have a street address?**
A: Both platforms hide street addresses by design — until the booking is confirmed. You'll get city + coordinate per row. Apartments.com publishes the full street address. This is also why FREE tier runs can produce empty `cross_listing_candidates[]` arrays — see the next question.

**Q: My `cross_listing_candidates` array is empty on most rows. Is this a bug?**
A: No — the 50 m radius is intentionally strict. Address-key joins across STR ↔ LTR fail roughly 95% of the time because Airbnb and Vrbo never expose street addresses (they hide them until the booking is confirmed), so the actor falls back to coordinate proximity + bedroom count + property type. Real cross-listings exist but they're rare in any given run. On FREE tier with only 25 rows per channel, the probability of two near-identical coordinates lining up is genuinely low. Run a denser market (NYC, LA, Miami Beach) on PAID tier with `maxResultsPerChannel: 200+` to see hit counts climb.

**Q: How accurate is the `monthly_equivalent_rate_usd`?**
A: The arithmetic is exact (`nightly × 30.4 × occupancy`). The one assumption you control is occupancy — `0.70` is industry default. For more accuracy, set `enrichSTRCalendar: true` to pull live forward-12-month calendars per Airbnb and Vrbo listing — that replaces the static baseline with real bookings data.

**Q: Why is the headline `marketComparisonRow` event only charged when 2+ channels return data?**
A: Because the rate normalization is only useful when there's something to compare it to. A single-channel run is just a vanilla scrape — you pay commodity rates only. The headline fires when the run produced rows on 2+ channels and the row carries a populated `monthly_equivalent_rate_usd`. Honest pricing.

**Q: Can I run this for non-US markets?**
A: Airbnb and Vrbo: yes (try `London, UK` or `Lisbon, Portugal`). Apartments.com: US only — the source is a US rental aggregator. Non-US runs will return Airbnb + Vrbo rows only, and the LTR baseline / break-even occupancy fields will be null.

**Q: How long does a typical run take?**
A: ~15 seconds for a 10-row quick scan, ~45 seconds for a 50-row standard sweep, ~3 minutes for a 50-row sweep with live calendar enrichment. Channels run in parallel — adding Vrbo to an Airbnb + Apartments run does NOT triple the wall-clock.

**Q: What output formats are available?**
A: JSON, CSV, Excel, JSONL, XML, RSS — export directly from the Apify dataset UI or pull via the API. The HTML market report is downloadable from the key-value store as `report.html`.

**Q: Is this legal?**
A: Yes — only publicly available listing data is consumed. See the legal section below.

### 🐞 Troubleshooting

**All channels return 0 rows for my location**

- Use `City, ST` format for US markets (`Miami, FL` not just `Miami`)
- Try the `coordinates` mode with a 10-20 km radius
- Confirm the location is spelled correctly (the upstream geocoder is strict)

**The `marketComparisonRow` event never fires**

- Only one channel returned data — widen the radius, pick a less-niche market, or accept commodity pricing on the per-channel events
- Check `channels_in_run` on the rows you got — if it's a single-element array, the headline gate intentionally blocks the charge

**Calendar enrichment didn't run**

- FREE tier blocks calendar enrichment regardless of the `enrichSTRCalendar` flag
- On PAID tier, confirm `enrichSTRCalendar: true` is set in your input

**Airbnb rows look truncated at 25 per run**

- That's the FREE-tier cap on `maxResultsPerChannel`. Upgrade to PAID tier for up to 500 rows per channel.

**Vrbo rows arrive with null `latitude` / `longitude`**

- Vrbo's search endpoint doesn't expose coordinates. Set `enrichSTRCalendar: true` to call the per-listing details endpoint, which populates coordinates and enables Vrbo rows in cross-listing detection.

### ⚖️ Is it legal to scrape data?

Our actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our actors, when used for ethical purposes by Apify users, are safe.

However, you should be aware that your results could contain personal data. Personal data is protected by the **GDPR** in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

You can also read Apify's blog post on the [legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

### ⚠️ Trademark Disclaimer

This is an **independent tool** developed by SIÁN Agency — it is not affiliated with, endorsed by, sponsored by, or otherwise officially connected to Airbnb, Inc., Expedia Group, Inc. (Vrbo), or CoStar Group, Inc. (Apartments.com). All product names, logos, and brand references appear in their nominative-fair-use form solely to describe the data the actor returns and the platforms it covers.

- **Airbnb** is a trademark of Airbnb, Inc.
- **Vrbo** is a trademark of Expedia Group, Inc.
- **Apartments.com** is a trademark of CoStar Group, Inc.

All trademarks are the property of their respective owners.

### 🤝 Support

[![Telegram Support](https://img.shields.io/badge/Telegram-Support%20Group-0088cc?logo=telegram)](https://t.me/+vyh1sRE08sAxMGRi)

**Join our active support community**

- For issues or questions, open an issue on the actor's page
- Check [SIÁN Agency Store](https://apify.com/sian.agency?fpr=sian) for more automation tools
- 📧 <apify@sian-agency.online>
- ⭐ Love this actor? [Leave a 5-star review](https://apify.com/sian.agency/str-investor-market-lens/reviews) — every review unlocks more dev time on this tool

***

**Built by [SIÁN Agency](https://www.sian-agency.online)** | **[More Tools](https://apify.com/sian.agency?fpr=sian)**

# Actor input Schema

## `searchMode` (type: `string`):

How to anchor the cross-channel pull. `location` accepts a city / state / neighbourhood string (e.g. `Miami, FL`). `coordinates` accepts a lat/lon + radius. `zip` accepts a US ZIP (Apartments.com primary; STR channels fall back to city via reverse-lookup).

## `location` (type: `string`):

Required when `searchMode = location`. Free-text destination — city, neighbourhood, or `City, ST`. Example: `Miami, FL`, `Brooklyn, NY`, `Lake Tahoe`.

## `latitude` (type: `string`):

Required when `searchMode = coordinates`. Decimal latitude (-90 to 90).

## `longitude` (type: `string`):

Required when `searchMode = coordinates`. Decimal longitude (-180 to 180).

## `radiusKm` (type: `integer`):

Used with `searchMode = coordinates`. Default 10 km. Larger radii return more rows but consume more credit.

## `zip` (type: `string`):

Required when `searchMode = zip`. 5-digit US ZIP. Apartments.com supports this natively; STR channels fall back to the city the ZIP resolves to.

## `channels` (type: `array`):

Which platforms to pull from. Default: all three. The headline `marketComparisonRow` event only fires when the query produced rows on **two or more** channels — so single-channel runs are charged commodity rates only.

## `maxResultsPerChannel` (type: `integer`):

Hard cap on rows pulled per channel. Free tier is capped at 25 per channel regardless.

## `occupancyBaseline` (type: `string`):

Default STR occupancy assumption when calendar enrichment is OFF. Used in `monthly_equivalent_rate = nightly × 30.4 × occupancy`. Industry default is `0.70` (70% bookings). Drop to `0.55` for soft markets, raise to `0.80` for top performers. Pass as a decimal string.

## `enrichSTRCalendar` (type: `boolean`):

When ON, fetches the forward-12-month availability calendar from Airbnb and Vrbo per listing — overrides the static occupancy baseline with live occupancy. Adds one upstream call per STR listing → priced separately as `strCalendarEnriched`.

## `computeCrossListingCandidates` (type: `boolean`):

When ON, every row carries a `crossListingCandidates[]` array listing peer-platform rows within 50 m + same bedrooms + same propertyType. Identifies likely 'same property on multiple channels' without an exact address join. Free — no extra charges.

## `checkIn` (type: `string`):

ISO date `YYYY-MM-DD`. When provided with `checkOut`, drives STR per-night prices. Leave blank for the static price the listing currently advertises.

## `checkOut` (type: `string`):

ISO date `YYYY-MM-DD`. Must be paired with `checkIn`.

## Actor input object example

```json
{
  "searchMode": "location",
  "location": "Miami, FL",
  "latitude": "25.77",
  "longitude": "-80.19",
  "radiusKm": 10,
  "zip": "33134",
  "channels": [
    "airbnb",
    "vrbo",
    "apartments"
  ],
  "maxResultsPerChannel": 50,
  "occupancyBaseline": "0.70",
  "enrichSTRCalendar": false,
  "computeCrossListingCandidates": true,
  "checkIn": "2026-06-15",
  "checkOut": "2026-06-20"
}
```

# Actor output Schema

## `results` (type: `string`):

One row per listing across the queried channels. Carries source\_platform, monthly\_equivalent\_rate\_usd, break\_even\_occupancy\_pct, and cross\_listing\_candidates.

## `htmlReport` (type: `string`):

HTML summary with STR vs LTR median monthly\_equivalent\_rate per channel, break-even occupancy distribution, and the cross-listing matches found.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("sian.agency/str-investor-market-lens").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("sian.agency/str-investor-market-lens").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 '{}' |
apify call sian.agency/str-investor-market-lens --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=sian.agency/str-investor-market-lens",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "STR vs LTR Market Lens — Airbnb, Vrbo & Apartments.com",
        "description": "STR vs LTR market lens for property investors. Pulls short-term (Airbnb, Vrbo) and long-term (Apartments.com) rates side-by-side, normalizes to monthly equivalents and break-even occupancy — the spreadsheet you'd build by hand, in one run.",
        "version": "1.0",
        "x-build-id": "8EnntXWpdXyMX2pg4"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/sian.agency~str-investor-market-lens/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-sian.agency-str-investor-market-lens",
                "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/sian.agency~str-investor-market-lens/runs": {
            "post": {
                "operationId": "runs-sync-sian.agency-str-investor-market-lens",
                "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/sian.agency~str-investor-market-lens/run-sync": {
            "post": {
                "operationId": "run-sync-sian.agency-str-investor-market-lens",
                "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",
                "required": [
                    "searchMode"
                ],
                "properties": {
                    "searchMode": {
                        "title": "🔍 Search mode",
                        "enum": [
                            "location",
                            "coordinates",
                            "zip"
                        ],
                        "type": "string",
                        "description": "How to anchor the cross-channel pull. `location` accepts a city / state / neighbourhood string (e.g. `Miami, FL`). `coordinates` accepts a lat/lon + radius. `zip` accepts a US ZIP (Apartments.com primary; STR channels fall back to city via reverse-lookup).",
                        "default": "location"
                    },
                    "location": {
                        "title": "📍 Location",
                        "type": "string",
                        "description": "Required when `searchMode = location`. Free-text destination — city, neighbourhood, or `City, ST`. Example: `Miami, FL`, `Brooklyn, NY`, `Lake Tahoe`."
                    },
                    "latitude": {
                        "title": "🌐 Latitude",
                        "type": "string",
                        "description": "Required when `searchMode = coordinates`. Decimal latitude (-90 to 90)."
                    },
                    "longitude": {
                        "title": "🌐 Longitude",
                        "type": "string",
                        "description": "Required when `searchMode = coordinates`. Decimal longitude (-180 to 180)."
                    },
                    "radiusKm": {
                        "title": "📏 Radius (km)",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Used with `searchMode = coordinates`. Default 10 km. Larger radii return more rows but consume more credit.",
                        "default": 10
                    },
                    "zip": {
                        "title": "📮 US ZIP code",
                        "type": "string",
                        "description": "Required when `searchMode = zip`. 5-digit US ZIP. Apartments.com supports this natively; STR channels fall back to the city the ZIP resolves to."
                    },
                    "channels": {
                        "title": "📡 Channels to query",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Which platforms to pull from. Default: all three. The headline `marketComparisonRow` event only fires when the query produced rows on **two or more** channels — so single-channel runs are charged commodity rates only.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "airbnb",
                                "vrbo",
                                "apartments"
                            ]
                        },
                        "default": [
                            "airbnb",
                            "vrbo",
                            "apartments"
                        ]
                    },
                    "maxResultsPerChannel": {
                        "title": "🔢 Max results per channel",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Hard cap on rows pulled per channel. Free tier is capped at 25 per channel regardless.",
                        "default": 50
                    },
                    "occupancyBaseline": {
                        "title": "📊 STR occupancy baseline (decimal)",
                        "type": "string",
                        "description": "Default STR occupancy assumption when calendar enrichment is OFF. Used in `monthly_equivalent_rate = nightly × 30.4 × occupancy`. Industry default is `0.70` (70% bookings). Drop to `0.55` for soft markets, raise to `0.80` for top performers. Pass as a decimal string.",
                        "default": "0.70"
                    },
                    "enrichSTRCalendar": {
                        "title": "📅 Pull live STR availability calendar",
                        "type": "boolean",
                        "description": "When ON, fetches the forward-12-month availability calendar from Airbnb and Vrbo per listing — overrides the static occupancy baseline with live occupancy. Adds one upstream call per STR listing → priced separately as `strCalendarEnriched`.",
                        "default": false
                    },
                    "computeCrossListingCandidates": {
                        "title": "🔗 Compute cross-listing candidates",
                        "type": "boolean",
                        "description": "When ON, every row carries a `crossListingCandidates[]` array listing peer-platform rows within 50 m + same bedrooms + same propertyType. Identifies likely 'same property on multiple channels' without an exact address join. Free — no extra charges.",
                        "default": true
                    },
                    "checkIn": {
                        "title": "📅 Check-in date (optional)",
                        "type": "string",
                        "description": "ISO date `YYYY-MM-DD`. When provided with `checkOut`, drives STR per-night prices. Leave blank for the static price the listing currently advertises."
                    },
                    "checkOut": {
                        "title": "📅 Check-out date (optional)",
                        "type": "string",
                        "description": "ISO date `YYYY-MM-DD`. Must be paired with `checkIn`."
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
