list-properties▌
realtor.ca/list-properties-5d4p1l · updated May 21, 2026
MDX-style export adds YAML metadata + attribution linking explainx.ai and this canonical listing URL.
List MLS-listed properties on REALTOR.ca within a bounding box or named Canadian city, filtered by sale/rental, price range, beds, and baths. Returns price, address, lat/lon, beds, baths, size, photo, agent, and canonical listing URL. Read-only.
| name | list-properties |
| title | REALTOR.ca List Properties |
| description | >- List MLS-listed properties on REALTOR.ca within a bounding box or named Canadian city, filtered by sale/rental, price range, beds, and baths. Returns price, address, lat/lon, beds, baths, size, photo, agent, and canonical listing URL. Read-only. |
| website | realtor.ca |
| category | real-estate |
| tags | - real-estate - listings - mls - canada - search - incapsula |
| source | 'browserbase: agent-runtime 2026-05-19' |
| updated | '2026-05-19' |
| recommended_method | hybrid |
| alternative_methods | - method: api rationale: >- POST api2.realtor.ca/Listing.svc/PropertySearch_Post returns up to 600 listings per bbox in one ~90KB JSON response with all displayed fields plus FloorAreaMeasurements and agent metadata. Works only from inside a Browserbase session that has visited realtor.ca to mint Incapsula reese84 + incap_ses_* cookies — pure curl from outside is challenge-walled. Hence 'hybrid', not 'api'. - method: browser rationale: >- Fallback path: navigate to /{province}/{city}/real-estate and scrape ~11 listing cards per page from rendered HTML. Use only when the API is blocked or you need rendered-DOM evidence. ~3x more turns and ~10x cost per listing harvested vs the API path. |
| verified | true |
| proxies | true |
REALTOR.ca List Properties
Purpose
Return a list of MLS®-listed properties on REALTOR.ca within a geographic bounding box (or within a named Canadian city), filtered by transaction type (sale or rental), property type group, price range, beds, and baths. For each match, return MLS number, price, address, lat/lon, property type, beds/baths, interior size, listing photo, and the canonical detail URL on realtor.ca. Read-only — never contacts an agent, never books a viewing, never modifies favourites or hidden-listing state.
When to Use
- "What's currently for sale in {city}/{neighbourhood} between $X and $Y with N+ beds?"
- Daily / hourly monitoring of new listings in a target area (sort by date-desc and dedupe by MlsNumber).
- Pulling all rental listings (
TransactionTypeId=3) in a metro area for market analysis. - Anywhere you'd otherwise scrape the rendered REALTOR.ca map — the JSON API is one POST per ≤600 results and exposes every field the UI shows (plus several it doesn't, like agent contact metadata and FloorAreaMeasurements).
Workflow
REALTOR.ca's /map and city-listing pages are thin clients over a single POST endpoint at api2.realtor.ca/Listing.svc/PropertySearch_Post. The endpoint sits behind Imperva/Incapsula (reese84, incap_ses_* cookies) so a bare curl from outside a real browser session always fails — you need a Browserbase session that has visited https://www.realtor.ca/ at least once to mint the challenge cookies. Once warmed, a page-context fetch() to the API returns up to 600 listings as JSON per call, ~90 KB, in 1–2 seconds. Lead with the API; the rendered HTML at /{province}/{city}/real-estate works as a fallback but only surfaces ~11 listing cards per page and costs ~3× more turns to harvest the same data.
1. Open a stealth + residential-proxy session
SID=$(browse cloud sessions create --keep-alive --verified --proxies --region us-east-1 \
| node -e 'let d="";process.stdin.on("data",c=>d+=c);process.stdin.on("end",()=>console.log(JSON.parse(d).id))')
export BROWSE_SESSION="$SID"
Both --verified and --proxies are mandatory. A bare session is served Incapsula's "Request unsuccessful" challenge HTML on the very first navigation. us-east-1 is the cheapest region that consistently routes through a Canadian-friendly proxy pool; if you need to scope to a specific province, you can re-request with a Canadian residential IP later, but the API does not geo-restrict by source IP.
2. Warm the session
browse open "https://www.realtor.ca/" --remote --session "$SID"
A single GET to the homepage is enough — it mints reese84, incap_ses_2105_*, GUID, Language=1, Currency=CAD, app_mode=1, and the AppInsights / GA tracking cookies. Do not skip this step. The PropertySearch_Post endpoint requires the Incapsula challenge tokens to be present on the request.
3. Resolve city or area to a lat/lon bounding box
The API only accepts a bounding box (LatitudeMin, LatitudeMax, LongitudeMin, LongitudeMax) plus a ZoomLevel (1–20, controls clustering / pin granularity). There is no city= parameter. Use these stable bboxes for the most-requested Canadian cities (verified 2026-05-19; pick a ZoomLevel of 11–13 to get individual pins instead of clusters):
| City | LatitudeMin | LatitudeMax | LongitudeMin | LongitudeMax | ZoomLevel |
|---|---|---|---|---|---|
| Toronto (City of) | 43.58 | 43.85 | -79.64 | -79.12 | 11 |
| Toronto (downtown core) | 43.63 | 43.68 | -79.43 | -79.35 | 13 |
| Vancouver | 49.20 | 49.32 | -123.27 | -123.02 | 11 |
| Calgary | 50.84 | 51.18 | -114.32 | -113.86 | 11 |
| Ottawa | 45.30 | 45.50 | -75.93 | -75.55 | 11 |
| Montreal | 45.40 | 45.71 | -73.98 | -73.47 | 11 |
| Edmonton | 53.39 | 53.71 | -113.71 | -113.30 | 11 |
| Hamilton | 43.20 | 43.30 | -80.00 | -79.75 | 12 |
| Mississauga | 43.50 | 43.65 | -79.78 | -79.55 | 12 |
| Oakville | 43.40 | 43.52 | -79.78 | -79.60 | 12 |
For arbitrary cities or neighbourhoods, do not try the api2.realtor.ca/Search.svc/AutoSuggest endpoint from page-context — it 0-status fails on CORS because the bundled XHR client adds custom headers that get pre-flighted (see Gotchas). Instead, navigate to https://www.realtor.ca/{province-code}/{city-slug}/real-estate (e.g., /on/toronto/real-estate, /bc/vancouver/real-estate, /ab/calgary/real-estate) and read window.__INITIAL_STATE__ or the active map bounds via:
browse open "https://www.realtor.ca/$PROV/$CITY/real-estate" --remote --session "$SID"
# Then either parse listing cards directly (fallback, ~11 per page) OR
# read the bbox the city page initialises its map with, then POST PropertySearch_Post.
Province codes are the standard two-letter ISO 3166-2:CA codes lowercased: on, bc, ab, qc, mb, sk, ns, nb, nl, pe, yt, nt, nu. City slugs are the city name lowercased with spaces → hyphens (new-westminster, prince-george).
4. POST PropertySearch_Post from the warmed session's page context
browse eval --remote --session "$SID" '
(async () => {
const params = new URLSearchParams({
ZoomLevel: "12",
LatitudeMin: "43.63", LatitudeMax: "43.68",
LongitudeMin: "-79.43", LongitudeMax: "-79.35",
Sort: "6-D", // 6-D = date-desc (newest first); 1-A = price-asc; 1-D = price-desc
PropertyTypeGroupID: "1", // 1 = Residential; 2 = Commercial
TransactionTypeId: "2", // 2 = For Sale; 3 = For Rent
PropertySearchTypeId: "0", // 0 = All residential subtypes
Currency: "CAD",
IncludeHiddenListings: "false",
RecordsPerPage: "50", // 1..200 sane; >200 server-caps to RecordsShowing=600 in one shot
ApplicationId: "1",
CultureId: "1", // 1 = en-CA; 2 = fr-CA
Version: "7.0",
CurrentPage: "1",
// Optional filters — append only the ones the caller asked for:
// PriceMin: "500000", PriceMax: "900000",
// BedRange: "2-0", // "2-0" = 2+ beds, no upper bound; "2-3" = 2..3 beds
// BathRange: "2-0", // same shape as BedRange
// Keywords: "waterfront pool",
// OpenHouse: "1",
});
const r = await fetch("https://api2.realtor.ca/Listing.svc/PropertySearch_Post", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
body: params.toString(),
});
return await r.json();
})()
'
PropertySearch_Post is the synchronous endpoint — it blocks until results are ready and returns them in one JSON payload. The site itself uses AsyncPropertySearch_Post followed by a long-poll, which is unnecessary overhead for a scripted client. Stick with the sync version.
A 200 OK response has:
{
"ErrorCode": { "Id": 200, "Description": "Success - OK", ... },
"Paging": {
"RecordsPerPage": 50,
"CurrentPage": 1,
"TotalRecords": 645, // total matches in the bbox/filter
"MaxRecords": 600, // hard server cap per bbox
"TotalPages": 13,
"RecordsShowing": 600, // == min(TotalRecords, MaxRecords)
"Pins": 436 // # of distinct map pins (may be < RecordsShowing if listings cluster)
},
"Results": [ /* 1..RecordsPerPage listings */ ],
"Pins": [ /* map-pin clusters, ignore for listing harvest */ ],
"GroupingLevel": 4
}
5. Decode each Results[] item
Each item is a fully-named object (no positional-array decoding like Craigslist). Map to a clean shape:
const out = j.Results.map(x => ({
mls_number: x.MlsNumber, // e.g. "C13141142"
realtor_id: x.Id, // realtor.ca internal id, used for /real-estate/{Id}/...
price: x.Property?.Price, // formatted string, e.g. "$740,000"
price_value: Number(x.Property?.PriceUnformattedValue), // numeric CAD
address: x.Property?.Address?.AddressText?.replace("|", ", "), // "1201 - 81 WELLESLEY STREET E, Toronto (Church-Yonge Corridor), Ontario M4Y0C5"
lat: Number(x.Property?.Address?.Latitude),
lon: Number(x.Property?.Address?.Longitude),
postal_code: x.PostalCode,
province: x.ProvinceName,
property_type: x.Property?.Type, // "Single Family", "Multi-family", "Vacant Land", ...
beds: x.Building?.Bedrooms, // "4 + 1" means 4 above-grade + 1 below
baths_total: x.Building?.BathroomTotal, // includes half-baths
baths_half: x.Building?.HalfBathTotal,
size_interior: x.Building?.SizeInterior, // e.g. "232.2557 m2"
floor_area: x.Building?.FloorAreaMeasurements?.[0]?.Area, // e.g. "2500+ sqft"
ownership: x.Property?.OwnershipType, // "Freehold", "Condominium", "Leasehold", ...
parking: x.Property?.Parking?.map(p => p.Name).join(", "),
parking_spaces: x.Property?.ParkingSpaceTotal,
photo_url: x.Property?.Photo?.[0]?.HighResPath,
remarks: x.PublicRemarks,
time_on_realtor: x.TimeOnRealtor, // human-readable: "3 min ago", "2 hours ago"
inserted_date_utc: x.InsertedDateUTC, // .NET ticks (see gotcha below)
url: "https://www.realtor.ca" + x.RelativeURLEn,
agent_name: x.Individual?.[0]?.Name,
agent_organization: x.Individual?.[0]?.Organization?.Name,
}));
6. Paginate if Paging.TotalRecords > RecordsShowing
The API returns at most MaxRecords (currently 600) per bbox regardless of RecordsPerPage. To get the rest, shrink the bbox (split into quadrants) rather than incrementing CurrentPage beyond ceil(MaxRecords/RecordsPerPage) — page numbers past that cap return empty Results. For dense areas (downtown Toronto pulls 11,902 total in one zoom-11 bbox), recursively split into four sub-bboxes until each is ≤600.
7. Release the session
browse cloud sessions update "$SID" --status REQUEST_RELEASE
Browser fallback
When the API is blocked (e.g., Incapsula challenge upgrade, residential proxy not available) or you need to confirm a listing's rendered state, open the city URL and scrape the cards:
browse open "https://www.realtor.ca/$PROV/$CITY/real-estate" --remote --session "$SID"
# Then read DOM:
browse eval --remote --session "$SID" '
Array.from(document.querySelectorAll("a[href*=\"/real-estate/\"]")).map(a => ({
url: a.href,
mls: (a.textContent.match(/MLS®:\s*(\S+)/) || [])[1],
price: (a.textContent.match(/\$[\d,]+/) || [])[0],
}))
'
Only ~11 listings per page, JS-driven pagination, expect ~3× more turns and ~10× the cost vs. the API path. Sort defaults to "Recent" (date-desc) and respects Filters URL params if you wire them in via the /map?Filters=... query string.
Site-Specific Gotchas
- READ-ONLY. Do not click "Save Listing", "Hide Listing", "Contact REALTOR®", or any heart/favourite icon — those require auth and would touch user state on shared session contexts.
- Imperva/Incapsula gates everything. A bare
curl https://api2.realtor.ca/Listing.svc/PropertySearch_Postfrom outside a real browser returns the Incapsula challenge HTML (1KB, 200 OK,<html>Request unsuccessful...</html>), not JSON. Even from a Browserbase session, you must visithttps://www.realtor.ca/first to mintreese84+incap_ses_*before the API call. Verified 2026-05-19: the homepage GET issuesreese84=3:...:...(Imperva sensor data fingerprint) plus 4 distinctincap_ses_*cookies tied to the WAF-protected sub-paths. browse cloud fetchis GET-only — it cannot POSTPropertySearch_Post. Usebrowse evalfrom a warmed session instead. Confirmed 2026-05-19 — fetch has no--method,--body, or--headerflags.AutoSuggestand other auxiliary endpoints fail CORS from page-context.GET https://api2.realtor.ca/Search.svc/AutoSuggest?text=oakville&CultureId=1&ApplicationId=1from insidebrowse evalreturnsstatus: 0(CORS pre-flight rejected) even on a warmed session. Stick toPropertySearch_Postfor the listing API; for city → bbox resolution, navigate to/{province}/{city}/real-estateand read map state or use the hardcoded bbox table above.- 600-record server cap per bbox.
Paging.MaxRecordsis server-fixed at 600.Paging.TotalRecordscan be 11,902 (downtown Toronto, zoom 12). To capture all matches, recursively subdivide the bbox until each sub-region'sTotalRecords ≤ 600. Naively requestingCurrentPage=7past the cap returns emptyResults[], not an error. AsyncPropertySearch_Postis a trap. The web UI itself usesAsyncPropertySearch_Postfollowed by a poll — that's two round-trips and useless for a scripted client. The synchronousPropertySearch_Postreturns the same data in one POST. Both endpoints share the same form-body schema.- Hash-based map navigation does not refetch.
browse open "https://www.realtor.ca/map#ZoomLevel=12&LatitudeMin=..."updates the URL hash but the JavaScript does not listen tohashchangefor filter refetches. To re-render the map for a new bbox you must either (a) make the API call directly (preferred) or (b) navigate tohttps://www.realtor.ca/map?with the bbox params as query, then wait forload. Bedroomsis a string and can be"4 + 1". Above-grade + below-grade splits are encoded as"N + M". Treat it as a string and parse defensively if you need a single integer.SizeInteriorunits are mixed. Sometimes"232.2557 m2", sometimes"2500 sqft", sometimes empty. TheFloorAreaMeasurements[0].AreaUnformattedfield carries the raw form ("2500-3000 sqft") if you need a range.Address.AddressTextuses|as a separator between street and city/province/postal:"3252 LARRY CRESCENT|Oakville (GO Glenorchy), Ontario L6M0S9". Split on|(max 1 split) to get street vs. locality.InsertedDateUTCis .NET ticks (100-ns intervals since 0001-01-01 UTC), not ISO 8601. Convert:epochMs = (ticks - 621355968000000000) / 10000. Most consumers should just use the human-readableTimeOnRealtor("3 min ago", "2 days ago") orTags[0].Label./{province}/{city}/real-estateis SEO-rendered with only 11 listings. It is NOT the same backend as/map— it's a server-rendered SEO page with classic pagination. Map view + API is ~50× faster per record harvested.- Currency defaults to CAD. Pass
Currency=USDto convert prices in the response — verified the API supports it but does not change the underlyingPriceUnformattedValuemapping to CAD. Sortcodes are positional-key:direction.1= price,6= inserted date,21= floor area. Append-A(ascending) or-D(descending).6-D= newest first;1-A= cheapest first.- Photo CDN is unauthenticated.
cdn.realtor.ca/listings/...images are fetchable without cookies — safe to surfaceHighResPathin your output. status: 0from page-context fetch == CORS block, not network failure. If you see this, the endpoint is preflighted; switch to a different transport (page navigation + DOM read) or skip the endpoint.- Verified flag does not persist
keep-aliveafterREQUEST_RELEASE. If youREQUEST_RELEASEand immediately re-create with the same flags, the new session gets a fresh challenge round — budget ~3 seconds extra for the first homepage GET.
Expected Output
{
"query": {
"bbox": { "lat_min": 43.63, "lat_max": 43.68, "lon_min": -79.43, "lon_max": -79.35 },
"zoom": 12,
"transaction": "sale",
"property_type_group": "residential",
"filters": { "price_min": 500000, "price_max": 900000, "beds_min": 2, "baths_min": 2 },
"sort": "date-desc"
},
"paging": {
"total_records": 645,
"records_showing": 600,
"records_returned": 50,
"current_page": 1,
"max_per_bbox": 600
},
"listings": [
{
"mls_number": "C13141142",
"realtor_id": "29767355",
"price": "$740,000",
"price_value": 740000,
"currency": "CAD",
"address": "1201 - 81 WELLESLEY STREET E, Toronto (Church-Yonge Corridor), Ontario M4Y0C5",
"lat": 43.6651,
"lon": -79.3793,
"postal_code": "M4Y0C5",
"province": "Ontario",
"property_type": "Single Family",
"beds": "2",
"baths_total": "2",
"baths_half": null,
"size_interior": "75.5 m2",
"floor_area": "700-800 sqft",
"ownership": "Condominium",
"parking": "Underground",
"parking_spaces": "1",
"photo_url": "https://cdn.realtor.ca/listings/TS.../highres/0/c13141142_1.jpg",
"remarks": "Bright south-facing 2-bed corner unit in the heart of Church-Yonge ...",
"time_on_realtor": "3 hours ago",
"url": "https://www.realtor.ca/real-estate/29767355/1201-81-wellesley-street-e-toronto-church-yonge-corridor",
"agent_name": "Jane Doe",
"agent_organization": "EXAMPLE REALTY INC."
}
]
}
For commercial searches, set PropertyTypeGroupID=2. For rentals, set TransactionTypeId=3. The listings[] schema is otherwise identical — rental prices come back as monthly strings ("$2,400 / Monthly").
When the API is unreachable (Incapsula challenge upgrade, no residential proxy available), emit a degraded payload from the city-page fallback:
{
"query": { "city": "toronto", "province": "on", "transaction": "sale" },
"paging": { "total_records": 10314, "records_returned": 11, "fallback": "city-page-html" },
"listings": [
{ "mls_number": "W13141174", "price": "$2,450,000",
"url": "https://www.realtor.ca/real-estate/29767472/4-robaldon-road-toronto-princess-rosethorn" }
]
}
How to use list-properties 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 list-properties
Execute installation command
Execute the skills CLI command in your project's root directory to begin installation:
The skills CLI fetches list-properties from GitHub repository realtor.ca/list-properties-5d4p1l 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 list-properties. Access the skill through slash commands (e.g., /list-properties) 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.5★★★★★59 reviews- ★★★★★Pratham Ware· Dec 24, 2024
list-properties is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Valentina Zhang· Dec 24, 2024
list-properties reduced setup friction for our internal harness; good balance of opinion and flexibility.
- ★★★★★Anika Gill· Dec 24, 2024
Useful defaults in list-properties — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Kabir Torres· Dec 20, 2024
Solid pick for teams standardizing on skills: list-properties is focused, and the summary matches what you get after install.
- ★★★★★Lucas Verma· Dec 8, 2024
We added list-properties from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
- ★★★★★Kaira Menon· Nov 27, 2024
Useful defaults in list-properties — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Valentina Martinez· Nov 15, 2024
Registry listing for list-properties matched our evaluation — installs cleanly and behaves as described in the markdown.
- ★★★★★William Harris· Nov 15, 2024
We added list-properties from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
- ★★★★★Zara Yang· Nov 11, 2024
list-properties has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Lucas Thomas· Nov 3, 2024
list-properties fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
showing 1-10 of 59