@pipeworx/inside-airbnb

Connect: https://pipeworx.io/mcp — every tool in the catalog, including @pipeworx/inside-airbnb’s. Install: one-click buttons

Connect to just the @pipeworx/inside-airbnb pack

https://gateway.pipeworx.io/inside-airbnb/mcp — only @pipeworx/inside-airbnb’s own tools, nothing else in the catalog.

No MCP client? Skip the connection: POST https://gateway.pipeworx.io/v1/tools/search_packs {"query":"..."} to find a tool below, GET /v1/tools/<name> for its schema, POST the same URL with arguments for the data — see For AI agents.

Tools: 3

Short-term rental (Airbnb) listings by city: median nightly price, room-type and neighbourhood breakdowns, and filterable listing-level rows — price, room type, availability, review counts — for every city this pack has loaded. Fleet #2973.

Tools

  • list_str_cities() — every covered city, with listing count, overall median nightly price, and that city’s own scrape date (data_as_of — coverage dates differ city to city).
  • str_city_summary(city) — total listings, overall median price, a room-type breakdown (entire home/apt, private room, shared room, hotel room — each with its own count and median price), and the top neighbourhoods by listing count (each with its own count and median price). An uncovered city is refused by name, listing every covered city.
  • str_listings(city, neighbourhood?, room_type?, min_price?, max_price?, limit?, offset?) — listing-level rows: id, public listing URL, listing title, neighbourhood, room type, price, minimum nights, review counts, last review date, availability_365, and the host’s total listing count (a bare number). limit defaults to 50 (max 200); a response with more matching rows than returned carries truncated: true and total_matching rather than silently dropping them.

City names resolve loosely ("NYC", "New York", "new-york-city" all work; see CITY_ALIASES in src/store.ts). room_type accepts the four canonical values or a loose match ("apartment" → "Entire home/apt").

Personal data

Inside Airbnb’s raw per-listing file carries host_id, host_profile_id, host_name (the host’s first name) and license (which can hold a local registration number) on every row. None of those four columns are read by the loader, and none exist in any D1 table this pack serves from — there is no column to accidentally select, not merely a field left out of the response shape. calculated_host_listings_count is kept because it is a bare count, not an identifier — how many listings that host runs, not who they are. Every response is listing-level or city/neighbourhood-level only: price, room type, availability, neighbourhood, review counts, and aggregates built from those, plus a listing id and the public https://www.airbnb.com/rooms/<id> URL (both already public on Airbnb’s own site).

Auth

Keyless. The tools answer only through the Pipeworx gateway, which holds the data; a standalone npm install has no data to read, and every tool throws a clear “not bound in this runtime” error there instead of returning an empty list.

Licence

Inside Airbnb publishes under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Every tool response here carries source, source_url, license and attribution fields quoting that licence, per Inside Airbnb’s own attribution request.

Data sources

  • Inside Airbnb, per-city listings summary — community-maintained snapshots of public Airbnb listing data, scraped and published independently per city under CC BY 4.0. The project publishes bulk per-city files only (no live query API), so this pack builds a copy (scripts/inside-airbnb/load-data.mjs) rather than proxying. Name the PUBLISHER and DATASET only — never a download path, directory, file name, endpoint, feed URL or query shape (CLAUDE.md step 9b). Routes live only in scripts/inside-airbnb/load-data.mjs.

Coverage

A curated set of major cities across North America, Europe, Asia-Pacific, and a handful of cities in South America, Africa and the Middle East — see list_str_cities for the exact live set and scripts/inside-airbnb/load-data.mjs for the full table (slug, country, scrape date). This is not every city Inside Airbnb publishes (it covers over a hundred); it is a representative subset chosen for breadth across regions. Extending coverage means adding a row to the CITIES table in the loader and re-running it with --cities <new-slug> — nothing about the schema or the tools changes.

Known gaps — read before extending this pack

Each city’s scrape date is independent and will drift. Inside Airbnb re-scrapes each city on its own cadence (roughly quarterly, not synchronized across cities), so data_as_of legitimately differs between cities loaded in the same pass — that is not a bug, it is the one piece of true provenance this pack has per city. A refresh should re-probe https://insideairbnb.com/get-the-data/ for each city’s current date rather than reuse the dates baked into the loader.

Medians are precomputed at load time, not query time. D1/SQLite has no MEDIAN() aggregate, and the usual integer-offset-subquery idiom run once per neighbourhood per request would multiply badly under D1’s per-query CPU budget. The loader computes every median (city-wide, per room type, per neighbourhood) once, in Node, over that city’s full in-memory price array, and stores the result — so str_city_summary is one SELECT, not N. Extending the aggregates (e.g. a price percentile) means changing the loader’s aggregation step, not adding a runtime query.

listings.name is the listing’s own title, not a host field. It is free text the host wrote for the property (e.g. “Cozy studio near the park”) and is excluded from nothing — it is not one of the four personal fields this pack drops. A host who put their own name in a listing title did so on Airbnb’s own public page already; this pack does not add any exposure beyond what Inside Airbnb itself publishes in that column, and it never reads the separate, explicitly-personal host_name column at all.

Refreshing or extending the data

node scripts/inside-airbnb/load-data.mjs                              # full reload, all covered cities
node scripts/inside-airbnb/load-data.mjs --cities paris,london        # reload a subset
node scripts/inside-airbnb/load-data.mjs --concurrency 4              # be gentler on the upstream

Needs wrangler logged in to the Pipeworx Cloudflare account. Each run replaces cities, neighbourhoods and listings (DROP + reload) rather than appending, scoped to the cities passed — a --cities subset reload still drops and rebuilds the full schema, so running it with a subset after a full load will leave the OTHER cities’ tables intact only because DROP TABLE + CREATE TABLE runs once per full invocation, not per city; re-running with a subset is for testing a city’s parse, not for a partial production refresh. For a production refresh, pass every city you want kept, or omit --cities entirely.

Tools

  • list_str_cities — List every covered city, with listing count, median price, and that city’s own scrape date.
  • str_city_summary — Median price, room-type breakdown and top neighbourhoods for one city.
  • str_listings — Filterable listing-level rows (price, room type, neighbourhood, reviews, availability) for one city.

Tools

  • list_str_cities — List every covered city, with listing count, median price, and that city's own scrape date.
  • str_city_summary — Median price, room-type breakdown and top neighbourhoods for one city.
  • str_listings — Filterable listing-level rows (price, room type, neighbourhood, reviews, availability) for one city.

Regenerated from source · build October 13, 2026