@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).limitdefaults to 50 (max 200); a response with more matching rows than returned carriestruncated: trueandtotal_matchingrather 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 inscripts/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.