Find Filtered Airbnb Stays

Site airbnb.comTask find-filtered-airbnb-staysVersion v1Updated Sep 3, 2026Category travel

Search Airbnb stays by location, dates, guests, bedroom count, price, property type, amenities, self check-in, Superhost status, and optionally verify listing-page amenities such as hot water, then extract listing details. This skill was captured from a live agent session on airbnb.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Find Airbnb listings matching location and lodging constraints such as minimum bedrooms, nightly price bounds, entire-home status, house or other property types, Wi-Fi, pool, self check-in, and Superhost status. Hostel preference can be handled as an optional ranking or content match when Airbnb exposes no reliable hostel-only parameter. Optionally verify amenities that are not exposed as reliable search filters, such as the exact listing-page amenity “Hot water.”

Use Cases

Use when the caller wants a filtered collection of Airbnb stays and the result should include listing IDs, titles, displayed prices, badges, descriptive content, and payment messages. Treat min_bedrooms=3 as at least three bedrooms; add date and guest parameters when availability or date-specific pricing is required. For amenities unavailable in search, such as hot water, search broadly and verify each candidate’s room page before returning it. For a hostel preference, leave results unfiltered unless a supported property-type parameter is known, then rank listings whose title, name, or content contains “hostel.”

Automation Flow

  1. Build and navigate directly to the Airbnb search URL, without visiting the homepage or using the search form: https://www.airbnb.com/s/{URL-encoded-location-slug}/homes?query={URL-encoded-location}&checkin={YYYY-MM-DD}&checkout={YYYY-MM-DD}&adults={guests}&min_bedrooms={minimum-bedrooms}&price_min={min-price}&price_max={max-price}&amenities%5B%5D=4&amenities%5B%5D=7&amenities%5B%5D=51&property_type_id%5B%5D=2&l2_property_type_ids%5B%5D=1&room_types%5B%5D=Entire%20home%2Fapt&self_check_in=true&superhost=true&display_currency=USD Omit unused filters rather than sending placeholder values. The location slug can be an Airbnb slug such as Mexico-City--Mexico; URL-encode location and query values. When Airbnb supplies them, optionally include place_id, acp_id, location_bb, and refinement_paths%5B%5D=%2Fhomes. Use checkin/checkout on search URLs and check_in/check_out on room URLs.
  2. If a consent banner is present, select Only necessary (or the equivalent necessary-cookies option) before extracting results. Locate the button by exact visible text; if selector clicking fails, use its visible bounding-box center rather than a fixed generic selector.
  3. In the same browser call, wait briefly for hydration (typically 3–7 seconds after navigation), then evaluate this extractor on the loaded search page:
    (() => {
      const state = document.querySelector('#data-deferred-state-0');
      if (!state) return {error:'NO_SSR', title:document.title, listings:[]};
      let data; try { data = JSON.parse(state.textContent); } catch (e) { return {error:'BAD_SSR_JSON', title:document.title, listings:[]}; }
      const results = data?.niobeClientData?.[0]?.[1]?.data?.presentation?.staysSearch?.results;
      if (!results) return {error:'NO_RESULTS', title:document.title, listings:[]};
      const decodeId = value => { try { return atob(value).replace(/^DemandStayListing:/, ''); } catch (e) { return null; } };
      const nums = value => (typeof value === 'string' ? value : '').match(/\d+(?:[.,]\d+)?/g)?.map(v => +v.replace(',', '.')) || [];
      return {
        total: results.searchResults?.length || 0,
        pageTitle: results.sectionConfiguration?.pageTitleSections?.sections?.[0]?.sectionData?.structuredTitle || null,
        pagination_cursors: results.paginationInfo?.pageCursors || [],
        listings: (results.searchResults || []).filter(item => item.demandStayListing).map(item => {
          const q = item.demandStayListing, p = item.structuredDisplayPrice?.primaryLine || {}, lines = item.structuredContent?.primaryLine || [];
          const baths = lines.find(x => x.type === 'BATHROOMINFO'), rating = nums(item.avgRatingLocalized || item.avgRatingA11yLabel), id = decodeId(q.id), badges = item.badges || [];
          const details = lines.map(x => x.body || '').join(' | ');
          const bedrooms = details.match(/\b(\d+)\s+bedrooms?\b/i), beds = details.match(/\b(\d+)\s+beds?\b/i);
          const reviewMatch = String(item.avgRatingA11yLabel || '').match(/([\d,]+)\s+reviews?\b/i) || String(item.avgRatingLocalized || '').match(/\(([\d,]+)\)/);
          const reviewCount = reviewMatch ? Number(reviewMatch[1].replace(/,/g, '')) : null;
          return {
            listing_id:id, url:id ? `https://www.airbnb.com/rooms/${id}` : null, title:item.title || null,
            name:item.name || item.nameLocalized?.localizedStringWithTranslationPreference || null,
            bedrooms:bedrooms ? Number(bedrooms[1]) : null, beds:beds ? Number(beds[1]) : null,
            bathrooms:baths ? nums(baths.body)[0] ?? null : null, lat:q.location?.coordinate?.latitude ?? null,
            lng:q.location?.coordinate?.longitude ?? null, coordinate_is_fuzzed:true,
            nightly_price:p.discountedPrice || p.price || null, nightly_price_original:p.originalPrice || null,
            price_qualifier:p.qualifier || null, price_a11y_label:p.accessibilityLabel || null,
            rating:rating[0] ?? null, review_count:reviewCount, badges:badges.map(x => x.text).filter(Boolean),
            superhost:badges.some(x => x.loggingContext?.badgeType === 'SUPERHOST'),
            guest_favorite:badges.some(x => ['GUEST_FAVORITE','TOP_TIER_FAVORITE'].includes(x.loggingContext?.badgeType)),
            content:lines.map(x => x.body || x.type || '').filter(Boolean).join(' | '),
            photo_url_primary:item.contextualPictures?.[0]?.picture || null,
            photo_urls:(item.contextualPictures || []).map(x => x.xlPicture || x.picture).filter(Boolean),
            payment_messages:item.paymentMessages || []
          };
        })
      };
    })()
  4. If an amenity such as hot water is requested, use each extracted listing_id to navigate directly to https://www.airbnb.com/rooms/{listing-id} (adding check_in, check_out, and guest parameters when relevant), then evaluate:
    (() => {
      const state = document.querySelector('#data-deferred-state-0');
      if (!state) return {error:'NO_SSR', url:location.href};
      let data; try { data = JSON.parse(state.textContent); } catch (e) { return {error:'BAD_SSR_JSON', url:location.href}; }
      const node = data?.niobeClientData?.[0]?.[1]?.data?.node?.pdpPresentation;
      if (!node) return {error:'NO_PDP_NODE', url:location.href};
      const groups = node.amenities?.seeAllAmenitiesGroups || [], amenities = groups.flatMap(g => (g.amenities || []).map(a => a.title)).filter(Boolean);
      return {listing_id:(location.pathname.match(/^\/rooms\/(\d+)/) || [])[1] || null, url:location.href, name:node.name || null, amenities, hot_water:amenities.includes('Hot water'), page_title:document.title};
    })()
    Keep only listings whose hot_water is true when required. Verify the returned URL and listing name against the search result because card navigation can open an unexpected listing.
  5. If more results are required, use the pagination cursor or the actual next-page href exposed in nav[aria-label="Search results pagination"] a. The visible Next control may fail to navigate; open its absolute or root-relative href directly, wait for hydration, rerun the same extractor, and concatenate results. Do not assume opaque listing IDs.

Possible Friction Points

  • Airbnb’s useful filter state is directly representable in query parameters; amenities[], min_bedrooms, price_min, price_max, property_type_id[], l2_property_type_ids[], room_types[], self_check_in, and superhost avoid fragile filter-dialog interaction.
  • Current Airbnb filter URLs observed for house searches use amenities[]=51 for self check-in, room_types[]=Entire home/apt, min_bedrooms={n}, and l2_property_type_ids[]=1. Retain the legacy property_type_id[]=2 house option when supported; Airbnb’s taxonomy has varied, so verify the resulting page’s applied filters.
  • Amenity 4 is Wi-Fi and amenity 7 is pool. Amenity 51 is the observed self-check-in filter.
  • Hot water was not exposed as a reliable search-filter option. Verify it on the room page using pdpPresentation.amenities.seeAllAmenitiesGroups and the exact amenity title Hot water.
  • No reliable hostel-only query parameter was established. Treat “hostel preferred” as an optional ranking/content preference unless a future run confirms a property-type enum; do not silently exclude other stays.
  • Search results embed structured data in #data-deferred-state-0; listing IDs are base64-encoded values such as DemandStayListing:{id} and must be decoded rather than guessed.
  • The room-page amenity data is also embedded in #data-deferred-state-0, under niobeClientData[0][1].data.node.pdpPresentation.amenities.seeAllAmenitiesGroups.
  • A short hydration wait is required before paginated SSR listings reliably appear.
  • A consent banner may block reliable interaction. The generic button[type='button']:nth-of-type(3) selector is unreliable; find the exact Only necessary button and, if needed, click its visible bounding-box center.
  • The visible pagination Next control may not navigate. Inspect its href and open that URL directly; observed next-page URLs may include pagination_search=true and an encoded cursor.
  • Card clicks can resolve to a different listing than intended. Prefer direct room URLs from decoded IDs and always verify the final URL and title/name.
  • price_min and price_max constrain Airbnb’s displayed search price, but exact nightly totals and availability depend on dates, guests, taxes, and fees.
  • Extreme price bounds can produce a reliable empty state such as “No exact matches” and “0 stays”; return an empty listings array rather than treating it as an extraction failure.
  • Search-result markup and the deferred-state schema are site-controlled and may change; the extractors intentionally return explicit missing-state errors.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=airbnb.com&task=find-filtered-airbnb-stays