rentfaster.ca logo
rentfaster.ca

filter-rentals-by-preferences

Installation

Adds this website's skill for your agents

 

Summary

Search RentFaster.ca's own JSON data feed and filter listings by pets, bedrooms, budget, in-suite laundry and move-in date, flagging anything a listing omits as 'unknown' rather than guessing. Read-only; returns link, price, size, pet rules, laundry, availability and coordinates per listing.

FIG. 01
FIG. 02
FIG. 03
SKILL.md
264 lines

Filter RentFaster.ca Rentals by Preferences

Purpose

Return RentFaster.ca rental listings that match a renter's preferences — pets, bedrooms, budget, in-suite laundry and move-in timing — by reading the site's own JSON data feed instead of driving the browser. A filtered search completes in a few seconds. Every preference a listing leaves blank is reported as "unknown" rather than guessed. Each result carries a canonical link, price, size (sq_feet), bed/bath counts, cat/dog rules, laundry type, availability and lat/lon coordinates. The skill can additionally rank results by walking distance to a place the renter cares about (using the returned coordinates) and dedupe against previously-seen listings. Read-only — it never contacts a landlord, applies, or books a tour.

When to Use

  • "Find me 2-bedroom Calgary rentals under $1,500 that allow cats and have in-suite laundry."
  • Daily/weekly monitoring where you only want listings you haven't seen before (dedupe by id).
  • Ranking candidate rentals by proximity to an office, a park, a river, or a transit stop.
  • Any flow that would otherwise scrape RentFaster HTML — the JSON feed is faster, cheaper, and not Cloudflare-blocked, whereas the HTML pages are.

Workflow

RentFaster.ca's UI is a thin client over a public JSON feed at https://www.rentfaster.ca/api/search.json. The HTML pages (/, /properties/...) sit behind Cloudflare's WAF and return HTTP 403 to any plain fetch, but the JSON feed returns 200. Lead with the feed; only fall back to the browser to read a single listing's floor-plan page.

1. Build the feed URL with the filters the API actually honors

GET https://www.rentfaster.ca/api/search.json
    ?proximity_type=location-city
    &novalidate=true
    &cities[]=Calgary
    &price_range_adv[from]=1000
    &price_range_adv[to]=1500
    &beds=2
    &cur_page=1

URL-encode the brackets (cities%5B%5D=, price_range_adv%5Bfrom%5D=). Fetch it:

browse cloud fetch "https://www.rentfaster.ca/api/search.json?proximity_type=location-city&novalidate=true&cities%5B%5D=Calgary&price_range_adv%5Bfrom%5D=1000&price_range_adv%5Bto%5D=1500&beds=2&cur_page=1" --proxies

Server-side filters the API honors (confirm acceptance via the echoed query object in the response): cities[] (repeatable), price_range_adv[from]/price_range_adv[to], beds (studio = 0), baths, type[] (e.g. Apartment, House, Condo), cur_page.

2. Read { listings, total, query } and paginate

The response is {"listings":[...48 per page...], "total":N, "query":{...}}. Read total and increment cur_page until covered (or cap at ~3 pages for a quick preview). query echoes exactly which params the server accepted — anything missing from it was silently dropped.

3. Filter CLIENT-SIDE for the preferences the API ignores

These params are accepted syntactically but do not filter the result set, so apply them yourself against each listing object:

  • Pets — use the per-listing booleans cats and dogs. Keep a listing only if the relevant boolean is true. If the field is null/absent, emit "unknown" and do not assume the pet is allowed.
  • In-suite laundry — inspect features[]. Laundry values observed: "Laundry - In Suite", "Laundry - Shared", "Laundry - Coin/Card". If features is empty/missing, laundry is "unknown". Do not use keywords= for laundry (see Gotchas).
  • Move-in date — availability (e.g. "Immediate") plus avdate (free text, may list several dates for multi-unit buildings, e.g. "Immediate, November 01"). Parse leniently; "unknown" when blank.

4. (Optional) Enrich: distance ranking and floor plans

  • Walking distance — every listing has latitude/longitude. Rank by distance to a target place: straight-line (haversine) is instant from the feed alone; for true walking distance, pass the coordinate pairs to an external routing service — the feed itself only provides coordinates, not routes.
  • Room sizes / floor plans — sq_feet is a CSV of unit sizes (e.g. "346, 522, 432"). When it is empty, say plainly "no room sizes given"; optionally open the listing's website (building site) or its /properties/... page via the browser fallback to look for floor plans. Never fabricate a size.

5. (Optional) Dedupe across repeat searches

Persist the set of ids you've already surfaced. On the next run, drop listings whose id is in that set so the renter only sees new inventory.

6. Emit compact JSON

Build the output object (see Expected Output). Cap listings at ~10 and report the full count in total_matched — emitting every field of hundreds of listings as prose will blow the response budget.

Browser fallback (only when you must render a listing page)

The feed covers the search/filter task completely. The only reason to touch the browser is to read a single listing's rendered floor-plan/photos when sq_feet is blank:

sid=$(browse cloud sessions create --keep-alive --verified --proxies | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).id))")
browse open "https://www.rentfaster.ca/properties/807-mcneill-road-northeast-calgary-301522" --remote --session "$sid"
browse wait load --remote --session "$sid"
browse get markdown body --remote --session "$sid"
browse cloud sessions update "$sid" --status REQUEST_RELEASE

Both --verified and --proxies are required for the HTML path — a bare session gets Cloudflare 403 / challenge.

Site-Specific Gotchas

  • HTML is Cloudflare-WAF'd; the JSON feed is not. A plain browse cloud fetch of / or any /properties/... page returns 403 (cloudflare, cloudflare-waf). search.json and map.json return 200 (their Cf-Cache-Status is DYNAMIC, not challenged).
  • Proxies are optional for the feed, mandatory for the browser fallback. search.json returns 200 both with and without --proxies from a datacenter IP (verified: identical totals). Keep --proxies on for resilience against rate-limiting and any geo edge cases, but the feed does not require residential egress. The browser/HTML path, by contrast, needs --verified --proxies or Cloudflare blocks it.
  • cats=1 / dogs=1 query params are silently ignored. Passing them returns the identical result set and distribution as omitting them (verified: 47 cats-true / 1 cats-false in the first page either way). Pets must be filtered client-side on the cats/dogs booleans.
  • keywords= mangles input. keywords=in-suite laundry is echoed back as "-suite laundry" — the parser strips/splits tokens. Don't rely on it for laundry or any structured filter; filter on features[] instead.
  • Flag, don't guess, missing fields. cats/dogs are usually booleans but can be absent; features is frequently empty (≈40 of 48 listings on a sample page had no laundry feature listed); sq_feet is occasionally blank. Each of these must surface as "unknown" / "no room sizes given" — never defaulted to a favourable value.
  • Bracket encoding is required. Unencoded cities[]= can be eaten by the shell/CLI and produce an empty response. Always send %5B%5D.
  • 48 results per page, fixed. per_page is ignored; paginate with cur_page. total is the full server-side count (e.g. Calgary ≈ 8,700 all-types; 2-bed $1000–$1500 ≈ 3,250).
  • avdate is free text and may be multi-valued. Multi-unit buildings list several dates ("Immediate, November 01"); don't parse it as a single ISO date.
  • Listing links are relative. The link field is /properties/<slug>-<id>; prefix https://www.rentfaster.ca to get a clickable URL.
  • ID-scoped refresh: proximity_type=location-id&id[]=<id> returns a single listing (plus a few related) — handy for re-checking one listing's current price/availability cheaply.
  • map.json is a lighter sibling (https://www.rentfaster.ca/api/map.json, up to 500 listings/page, ~31,900 nationwide) but omits features, cats/dogs, sq_feet. Use search.json when you need the preference fields.
  • No site-specific CAPTCHA observed on the JSON feed across the iterations tested — the only anti-bot surface is Cloudflare on the HTML pages.

Expected Output

{
  "success": true,
  "city": "Calgary",
  "total_matched": 15,
  "filters": {
    "beds": 2,
    "price_from": 1000,
    "price_to": 1500,
    "cats": true,
    "laundry": "in-suite",
    "move_in": "asap"
  },
  "listings": [
    {
      "id": 301522,
      "url": "https://www.rentfaster.ca/properties/807-mcneill-road-northeast-calgary-301522",
      "title": "NEWLY RENOVATED 2 BDRM, Split Level in Mayland Heights",
      "price": "1500",
      "sq_feet": "950",
      "beds": "2",
      "baths": "1",
      "cats": true,
      "dogs": true,
      "laundry": "in-suite",
      "availability": "Immediate",
      "avdate": "Immediate",
      "latitude": 51.058088,
      "longitude": -114.016964
    }
  ],
  "error_reasoning": null
}

A listing with fields the feed omitted (honest "unknown" reporting):

{
  "id": 784644,
  "url": "https://www.rentfaster.ca/properties/some-building-calgary-784644",
  "title": "2 Bedroom Apartment",
  "price": "1425",
  "sq_feet": "no room sizes given",
  "beds": "2",
  "baths": "1",
  "cats": true,
  "dogs": "unknown",
  "laundry": "unknown",
  "availability": "Immediate",
  "avdate": "unknown",
  "latitude": 51.0441,
  "longitude": -114.0631
}

No matches after filtering:

{
  "success": true,
  "city": "Calgary",
  "total_matched": 0,
  "filters": { "beds": 3, "price_from": 800, "price_to": 1000, "cats": true, "laundry": "in-suite" },
  "listings": [],
  "error_reasoning": null
}

Feed unreachable / blocked:

{
  "success": false,
  "city": "Calgary",
  "total_matched": 0,
  "listings": [],
  "error_reasoning": "search.json returned HTTP 403 (Cloudflare) — retry with --proxies or back off and retry."
}
Filter RentFaster.ca Rentals by Preferences · browse.sh