find-a-hotel▌
hotpads.com/find-a-hotel-7v8ssy · updated May 21, 2026
MDX-style export adds YAML metadata + attribution linking explainx.ai and this canonical listing URL.
Search HotPads for rental listings (apartments, houses, condos, townhomes, rooms, sublets) in a city, neighborhood, ZIP, or lat/lon bounding box. Returns address, rent range, beds, baths, sqft, photos, amenities, and detail-page URL per listing. Read-only.
| name | find-a-hotel |
| title | HotPads Find a Rental |
| description | >- Search HotPads for rental listings (apartments, houses, condos, townhomes, rooms, sublets) in a city, neighborhood, ZIP, or lat/lon bounding box. Returns address, rent range, beds, baths, sqft, photos, amenities, and detail-page URL per listing. Read-only. |
| website | hotpads.com |
| category | real-estate |
| tags | - rentals - apartments - real-estate - listings - hotpads - zillow |
| source | 'browserbase: agent-runtime 2026-05-15' |
| updated | '2026-05-15' |
| recommended_method | api |
| alternative_methods | - method: browser rationale: >- Use only when the internal JSON API at hotpads-api-gke-prod-1-west-20250228-public.hotpads.com is unreachable. The public HTML at hotpads.com/{resource}/apartments-for-rent is fronted by PerimeterX (appId PXxOR1K5b6) and requires --verified --proxies; popular-city HTML responses exceed browse cloud fetch's 1MB cap so a full Browserbase browser session is required to read window.__PRELOADED_STATE__. |
| verified | false |
| proxies | false |
HotPads Find a Rental
Purpose
Search HotPads (Zillow Group's rental marketplace) for rentals in a city, neighborhood, or arbitrary lat/lon bounding box — returning each listing's title (building or address), street address, monthly rent (min/max), bedrooms (min/max), bathrooms, square footage, property type, lat/lon, photo URL, and canonical detail-page URL. Read-only; never sends inquiries, applications, or "Contact" form submissions.
The site name has "pads" — these are rental apartments / houses / condos / townhomes / rooms / sublets, not hotels. If a user asks for a "hotel" on HotPads, interpret it as "find a place to stay / rent" and return rentals.
When to Use
- "Apartments under $3000 in San Francisco with 1+ bedroom."
- Bulk extraction of rentals in a city, neighborhood, ZIP code, or county.
- Map-bounded searches (e.g., "rentals within these lat/lon corners near the user's office").
- Anywhere you'd otherwise scrape the HotPads HTML grid — the internal JSON API is faster, cheaper, and avoids the PerimeterX captcha on the HTML surface entirely.
Workflow
HotPads' web UI is a Next-style thin client over a public-looking JSON API hosted at hotpads-api-gke-prod-1-west-20250228-public.hotpads.com (the hostname is stable across pages and is exposed in window.__PRELOADED_STATE__.location.ssrEntry.requests on every search page). Two endpoints do the entire job — area/byResourceId to resolve a city slug into an areaId + bounding box, then listing/byCoordsV2 to fetch the listings. No auth, no cookies, no CSRF, no PerimeterX challenge — the API is reachable from a bare browse cloud fetch without --proxies. Lead with the API; the HTML/browser path costs ~100× more (PerimeterX captcha-blocks bare requests, city pages are > 1MB so browse cloud fetch 502s on them, and you'd need a Verified + residential-proxy browser session).
-
Resolve the area — turn a user's city name into a canonical HotPads
resourceId. The format is<city-slug-hyphenated>-<state-code-lowercase>(e.g.san-francisco-ca,new-york-ny,brooklyn-new-york-ny,topeka-ks,austin-tx). For neighborhood- or ZIP-scoped searches, the resourceId is the neighborhood / ZIP slug (e.g.mission-san-francisco-ca,94110-ca). Then call:GET https://hotpads-api-gke-prod-1-west-20250228-public.hotpads.com/hotpads-api/api/v2/area/byResourceId ?resourceId={resourceId}Returns
data.{id, name, type, city, state, county, minLat, maxLat, minLon, maxLon, uriV2}. Theid(e.g.1112868274for San Francisco) is the numeric areaId you pass to the listings endpoint. The fourmin/max Lat/Lonfields are the city's bounding box.typeis one ofcity,neighborhood,zip,borough,county,state.Save discovered areaIds to a local cache — they are stable. Confirmed at 2026-05-15: SF
1112868274, NYC117776782, Boston1299308461, Chicago2067068844, Austin216213232, Topeka1292505385, Brooklyn (borough)391588231. -
Search for listings:
GET https://hotpads-api-gke-prod-1-west-20250228-public.hotpads.com/hotpads-api/api/v2/listing/byCoordsV2 ?areas={areaId} &minLat={}&maxLat={}&minLon={}&maxLon={} &searchSlug={apartments-for-rent|houses-for-rent|condos-for-rent|townhomes-for-rent|rooms-for-rent|...} &listingTypes=rental,room,sublet,corporate &propertyTypes=condo,divided,garden,house,large,medium,townhouse &bedrooms=1,2,3,4,5,6,7,8plus &bathrooms=0,0.5,1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7,7.5,8plus &orderBy=score &limit=200 &components=basic,useritem,quality,model,photos &trimResponse=trueUse the bbox from step 1 verbatim. Returns:
{ "data": { "numListingsAvailable": 982, // total matching listings in the bbox "numBuildingsAvailable": 982, // total matching buildings "numListingsIncluded": 212, // listings actually returned (may exceed buildings.length when a building has multiple listings) "buildings": [ { "lotIdEncoded": "sknnxb", "geo": { "lat": 39.13, "lon": -95.71, "quad": "..." }, "uri": "/emory-lakes-luxury-apartments-topeka-ks-66618-sknnxb/building", "listings": [ { /* see step 3 */ } ], "neighborhoods": [...] } ] } } -
Decode each listing. Within
data.buildings[i].listings[j], the relevant fields are:Field Meaning titleBuilding name (e.g. "Emory Lakes Luxury Apartments") or nullfor individual houses (useaddress.streetin that case)address.{street, city, state, zip, hideStreet}Postal address. hideStreet: truemeans HotPads suppresses the exact street; surface onlycity, state, zip.propertyTypelarge(mid/high-rise apartment complex),medium,garden,house,townhouse,condo,divided,landlistingTyperental,room,sublet,corporatemodelSummary.{minPrice, maxPrice, minBeds, maxBeds, minBaths, maxBaths, minSqft, maxSqft}Aggregate price/bed/bath/sqft across all units in the building models[]Per-floorplan breakdown: {numBeds, lowPrice, highPrice}uriMaloneCanonical detail-page path. Construct full URL as https://hotpads.com{uriMalone}(e.g./emory-lakes-luxury-apartments-topeka-ks-66618-sknnxb/pad)medPhotoUrlPrimary thumbnail at 500×500 photoCountTotal photos on the detail page amenities.highlightedAmenities[]{persisted, display, subtypes: [{persisted, display}]}— top 5 amenities. Commonpersistedkeys:pets,laundry,hvac,gym,parking,outdoorAreas,dishwasherhasSpecialOfferstrue → building advertises a promo (free month, waived fees) trustedtrue → verified by HotPads ops (paid multifamily listing) incomeRestricted,seniorHousing,studentHousing,militaryHousingSubsidized-housing flags The building's
uriends in/building; the listing'suriMaloneends in/pad(or/pad-for-sublet). Both render; preferuriMalonefor the unit detail andurifor the building overview. -
Apply user filters as query params. Confirmed working:
maxPrice=<N>,minPrice=<N>. Narrowbedrooms=to a subset (e.g.bedrooms=1,2) or usebedrooms=studio,1,2,3,4,5,6,7,8plus. NarrowlistingTypes=(rentalonly for non-shared apartments; addroomfor shared housing;subletfor short-term). NarrowpropertyTypes=(house,townhouseto exclude apartment buildings,large,medium,gardento focus on apartments). The full enum sets are the defaults shown above — drop categories to filter them out. Unrecognized params are silently dropped, so always verify the returnednumListingsAvailablereflects your intent. -
Paginate via bbox subdivision (not via
start=/offset=). The API has a hard cap of ~200 buildings per response —limit=1000returns the same 200 aslimit=200. Thestart=param is the HTML-only pagination knob and is blanket-Disallow-ed in robots.txt; the API ignores it. To enumerate beyond 200, split the lat/lon bounding box in half and re-query each half:const midLat = (minLat + maxLat) / 2; const leftHalf = { minLat, maxLat, minLon, maxLon: (minLon+maxLon)/2 }; const rightHalf = { minLat, maxLat, minLon: (minLon+maxLon)/2, maxLon }; // Recurse if a half still returns 200 buildings.In practice, for cities with < 200 results in the default bbox (Topeka 178, San Francisco at the default filter set above returns 982 so you'd need ~5 tiles), one or two splits is enough. Dedupe by
building.lotIdEncodedafter merging. -
Order —
orderBy=score(default, HotPads relevance ranking — favors paid + trusted listings),weekViews,price(asc),priceHighToLow,recencyTime(newest first). Note robots.txt forbids*orderByon HTML URLs; the API accepts it freely.
Browser fallback (only when the API path returns 5xx — historically rare)
If hotpads-api-gke-prod-1-west-20250228-public.hotpads.com is unreachable, fall back to the public HTML at https://hotpads.com/{resourceId}/apartments-for-rent (or /houses-for-rent, etc.). This path requires a Verified + residential-proxy browser session because hotpads.com is fronted by PerimeterX (Human Security; appId: PXxOR1K5b6, captcha URL /xOR1K5b6/captcha/...) — bare browse cloud fetch and bare browser sessions get a 403 px-captcha interstitial. browse cloud fetch --proxies bypasses the captcha for the HTML page but the rendered listing-grid HTML for any popular city exceeds 1 MB, which is the browse cloud fetch body cap → 502 ("response body exceeded the maximum allowed size of 1MB"). So the realistic fallback is a full Browserbase browser session:
SID=$(browse cloud sessions create --keep-alive --verified --proxies | jq -r '.id')
browse cloud browse --connect "$SID" open "https://hotpads.com/{resourceId}/apartments-for-rent"
browse cloud browse --connect "$SID" wait load
# Listings are embedded as JSON inside <script>window.__PRELOADED_STATE__ = {...}</script>
browse cloud browse --connect "$SID" get html body > body.html
node -e 'const html=fs.readFileSync("body.html","utf8");
const idx=html.indexOf("window.__PRELOADED_STATE__");
// Walk braces from the first { after "=" to extract the JSON blob,
// then read state.listings.listingGroups.byCoords[] — same shape as the API,
// but limited to ~40 items per request (the SSR pre-render slice).'
browse cloud sessions update "$SID" --status REQUEST_RELEASE
The HTML page returns ~40 listings in __PRELOADED_STATE__.listings.listingGroups.byCoords (same field names as the API), plus a seoFooterLinks block that lists ~20 related sub-searches (price-bucketed, bedroom-bucketed, pet-friendly, etc.) you can use as discovery URIs.
Site-Specific Gotchas
- HotPads is a rental marketplace, not a hotel marketplace. The skill name
find-a-hotelis a misnomer at the slug level; the site only lists rentals (apartments, houses, condos, townhomes, rooms, sublets, corporate housing). If the calling agent really wants a hotel, route them to a hotel-specific site (Booking, Expedia, Google Hotels) — HotPads will return apartments even for hotel-shaped queries. - PerimeterX guards the HTML, not the API.
https://hotpads.com/*returns a 403 px-captcha interstitial (appId=PXxOR1K5b6, served from/xOR1K5b6/captcha/captcha.js) onbrowse cloud fetchwithout--proxies. The api hosthotpads-api-gke-prod-1-west-20250228-public.hotpads.comis not behind PX and accepts bare requests. Verified 2026-05-15 with five distinct city resourceIds from a non-residential IP — all 200 with full JSON bodies and zero captcha challenge. browse cloud fetch1MB cap kills the HTML path for popular cities. SF / NYC / Chicago rental list pages render at 1.2–1.5 MB after Next hydration —browse cloud fetch502s with "response body exceeded the maximum allowed size of 1MB". Small / mid markets (Topeka 860 KB, Billings 920 KB) squeak under. The API path has no body-size limit.- resourceId is the URL-slug, not the human name.
San Francisco, CA→san-francisco-ca.New York, NY→new-york-ny.Brooklyn, NY→brooklyn-new-york-ny(not justbrooklyn-ny— borough resourceIds include the parent city).Mission District, San Francisco→mission-san-francisco-ca. ZIP-scoped:94110-ca. If the slug fails with 4xx, fall back to: (a) the HTML autocomplete UI on hotpads.com via a browser session, or (b) brute-force candidates by stripping/adding parent-city segments. The/api/v2/area/autocompleteendpoint exists but its query param name was not determined during testing (it returnsINSUFFICIENT_DATA: Number of chars is below the minimum requirement=2for everyq=/query=/term=/text=/s=variant tried) — fall back to direct resourceId guessing or the SSRseoFooterLinksdiscovery block (which lists related-area URIs asuriV2fields). buildings[]vs.numListingsIncluded. The API returns at most 200 buildings, but a building can have multiple listings (different floorplans, sublease vs. lease, etc.).numListingsIncludedcan exceedbuildings.length. For "give me N listings", iteratebuildings[i].listings[j]flat.numListingsAvailable === numBuildingsAvailablein practice. Despite the field-name difference, both report the building-level total. To get unit-level totals, summodelTypeUnitCountornumUnitsfrom thedataobject (Topeka returnednumUnits: 366matching the "366 Rentals" header).hideStreet: truelistings. HotPads suppresses the exact street address for some single-family rentals (privacy / anti-scraper). Whenaddress.hideStreetis true, emit onlycity, state, zipand theuriMaloneURL — do not synthesize a street.title: nullis legitimate. Single-family houses often have no building/complex name. Fall back toaddress.street(oraddress.city + stateifhideStreet) for display.- Asterisks /
+in displayed prices.listingMinMaxPriceBeds.priceDisplay = "$1,264+"means "from $1,264, but exact varies";priceDisplayRange = "$1,264 - $1,651"is the resolved range. Prefer numericmodelSummary.minPrice/maxPricefor downstream logic. start=is not a real pagination param on the API. Despite robots.txt'sDisallow: /*start=(which suggests it exists on the HTML side), the API ignoresstartentirely — verified by passingstart=5and getting the same first 5 buildings asstart=0. Use bbox subdivision for > 200 results.limitcaps at 200. Anything higher returns 200.limit=40is the SSR default;limit=200is the practical max.maxPricefilters at the building level, not the unit level. AmaxPrice=4000query in SF returns buildings where AT LEAST ONE unit is ≤ $4000 — so the response'smodelSummary.maxPricecan be $8,000+ for a building whose cheapest studio is $3,500. Re-filter onmodelSummary.minPrice <= maxPriceclient-side if you want strictly-affordable buildings, or readmodels[].lowPriceto identify the qualifying floorplans.- No JSON-LD listings index on city pages. The city
SearchResultsPageJSON-LD block is metadata-only (contentLocation,about,breadcrumb) — it does not carrymainEntity: [...listings]. Listings are only in__PRELOADED_STATE__. Don't waste a parse pass on<script type="application/ld+json">for the list view. Individual/paddetail pages do carry@type: ApartmentComplexwith address + geo + amenities (no pricing). __PRELOADED_STATE__is one massive JS literal, not JSON5. It uses single-line-comment-free strict JSON but the closing;</script>requires a brace walker (count{/}while respecting string-quote state) — a regex({[\s\S]*?});will mismatch on nested braces. The blob is ~320 KB; extracting it on each page load is fine. Listing array path:state.listings.listingGroups.byCoords(the SSR-rendered 40). Other groups (viewed,favorite,hidden,inquired,mostPopular,petFriendly) are user-personalized and empty for a cookieless session.- PerimeterX
appIdisPXxOR1K5b6(note lowercase x, capital O). Robots.txt listsDisallow: /xOR1K5b6/confirming this is the canonical PX endpoint. If a future PX appId rotation breaks the residential-proxy path, search the 403 body for_pxAppIdto grab the new value. - Sort
weekViewsis a leaky signal of demand, not price/recency. UseorderBy=recencyTimefor "newest",orderBy=pricefor cheapest first. - READ-ONLY. Never POST to
/hotpads-api/api/v2/user/item/create,/event/trigger,/inquiry/completed.htm, or theContact/ "Apply now" / "Send message" buttons — those create tracked leads charged to the landlord. - Internal API hostname is dated. The current host string
hotpads-api-gke-prod-1-west-20250228-public.hotpads.comembeds a deploy date (20250228). If it 404s in the future, re-discover by fetching any small city's search page (e.g.https://hotpads.com/topeka-ks/apartments-for-rentviabrowse cloud fetch --proxies) and reading__PRELOADED_STATE__.location.ssrEntry.requests[].url— the current API hostname is the prefix of every entry.
Expected Output
{
"success": true,
"query": {
"resource_id": "san-francisco-ca",
"area_id": "1112868274",
"area_name": "San Francisco",
"area_type": "city",
"bbox": { "min_lat": 37.7076, "max_lat": 37.8429, "min_lon": -122.5367, "max_lon": -122.3299 },
"search_slug": "apartments-for-rent",
"filters": { "max_price": 4000, "min_bedrooms": 1, "listing_types": ["rental"] }
},
"total_listings_available": 982,
"total_buildings_available": 982,
"listings_returned": 10,
"listings": [
{
"title": "NEMA",
"street": "8 10th St",
"city": "San Francisco",
"state": "CA",
"zip": "94103",
"hide_street": false,
"lat": 37.7758,
"lon": -122.4159,
"property_type": "large",
"listing_type": "rental",
"rent_min": 3750,
"rent_max": 7925,
"rent_display": "$3,750 - $7,925",
"beds_min": 0,
"beds_max": 2,
"baths_min": 1,
"baths_max": 2,
"sqft_min": 451,
"sqft_max": 1240,
"models": [
{ "beds": 0, "low_price": 3750, "high_price": 4200 },
{ "beds": 1, "low_price": 4100, "high_price": 5800 },
{ "beds": 2, "low_price": 5900, "high_price": 7925 }
],
"url": "https://hotpads.com/nema-san-francisco-ca-94103-249xqhy/pad",
"building_url": "https://hotpads.com/nema-san-francisco-ca-94103-249xqhy/building",
"photo": "https://photos.zillowstatic.com/fp/.../rentals_medium_500_500.webp",
"photo_count": 38,
"amenities": ["pets:catsAndDogs", "laundry:inUnit", "gym:on-site", "parking:garage"],
"has_special_offers": false,
"trusted": true,
"income_restricted": false,
"lot_id_encoded": "sknnxb",
"alias_encoded": "fu5dc59wj3ge"
}
],
"error_reasoning": null
}
Failure shapes:
// resourceId not found
{ "success": false, "error_reasoning": "resourceId 'sn-fransicso-ca' returned 4xx from /area/byResourceId — try fuzzy match or check spelling", "query": {...}, "listings": [] }
// API reachable, zero matching listings
{ "success": true, "total_listings_available": 0, "total_buildings_available": 0, "listings_returned": 0, "listings": [], "query": {...} }
// API down → had to fall back to browser, PX captcha not solvable
{ "success": false, "error_reasoning": "API endpoint returned 5xx; browser fallback hit PerimeterX captcha (appId PXxOR1K5b6) — try a residential proxy session or retry later", "query": {...}, "listings": [] }
How to use find-a-hotel on Cursor
AI-first code editor with Composer
Prerequisites
Before installing skills in Cursor, ensure your development environment meets these requirements:
- ›Cursor installed and configured on your development machine
- ›Node.js version 16.0+ with npm package manager (verify with
node --version) - ›Active project directory or workspace where you want to add find-a-hotel
Execute installation command
Execute the skills CLI command in your project's root directory to begin installation:
The skills CLI fetches find-a-hotel from GitHub repository hotpads.com/find-a-hotel-7v8ssy and configures it for Cursor.
Select Cursor when prompted
The CLI will show a list of available agents. Use arrow keys to navigate and space to select Cursor:
Verify installation
Confirm successful installation by checking the skill directory location:
Reload or restart Cursor to activate find-a-hotel. Access the skill through slash commands (e.g., /find-a-hotel) or your agent's skill management interface.
Security & Verification Notice
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your development environment. Always verify the publisher's identity, review recent commits, and test in isolated environments before production deployment.
List & Monetize Your Skill
Submit your Claude Code skill and start earning
Use Cases▌
Task Automation & Efficiency
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Knowledge Enhancement
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Quality Improvement
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
Implementation Guide▌
Prerequisites
- ›Claude Desktop or compatible AI client with skill support
- ›Clear understanding of task or problem to solve
- ›Willingness to iterate and refine outputs
Time Estimate
15-45 minutes depending on use case complexity
Installation Steps
- 1.Install skill using provided installation command
- 2.Test with simple use case relevant to your work
- 3.Evaluate output quality and relevance
- 4.Iterate on prompts to improve results
- 5.Integrate into regular workflow if valuable
Common Pitfalls
- ⚠Expecting perfect results without iteration
- ⚠Not providing enough context in prompts
- ⚠Using skill for tasks outside its intended scope
- ⚠Accepting outputs without review and validation
Best Practices▌
✓ Do
- +Start with clear, specific prompts
- +Provide relevant context and constraints
- +Review and refine all outputs before using
- +Iterate to improve output quality
- +Document successful prompt patterns
✗ Don't
- −Don't use without understanding skill limitations
- −Don't skip validation of outputs
- −Don't share sensitive information in prompts
- −Don't expect skill to replace human judgment
💡 Pro Tips
- ★Be specific about desired format and style
- ★Ask for multiple options to choose from
- ★Request explanations to understand reasoning
- ★Combine AI efficiency with human expertise
When to Use This▌
✓ Use When
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid When
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
Learning Path▌
- 1Familiarize yourself with skill capabilities and limitations
- 2Start with low-risk, non-critical tasks
- 3Progress to more complex and valuable use cases
- 4Build expertise through regular use and experimentation
Discussion
Product Hunt–style comments (not star reviews)- No comments yet — start the thread.
Ratings
4.6★★★★★34 reviews- ★★★★★Soo Torres· Dec 16, 2024
find-a-hotel is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Kwame Khan· Dec 12, 2024
I recommend find-a-hotel for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
- ★★★★★Ira Bansal· Dec 8, 2024
find-a-hotel reduced setup friction for our internal harness; good balance of opinion and flexibility.
- ★★★★★Kabir Gill· Nov 3, 2024
Useful defaults in find-a-hotel — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Ishan Garcia· Oct 22, 2024
Registry listing for find-a-hotel matched our evaluation — installs cleanly and behaves as described in the markdown.
- ★★★★★Oshnikdeep· Sep 21, 2024
find-a-hotel fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
- ★★★★★Meera Khanna· Sep 1, 2024
We added find-a-hotel from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
- ★★★★★Ishan Martinez· Aug 20, 2024
Keeps context tight: find-a-hotel is the kind of skill you can hand to a new teammate without a long onboarding doc.
- ★★★★★Ganesh Mohane· Aug 12, 2024
find-a-hotel has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Sophia Zhang· Jul 11, 2024
find-a-hotel has been reliable in day-to-day use. Documentation quality is above average for community skills.
showing 1-10 of 34