Real Estate SaaS MLS Integration & Property Data

Developers new to proptech all make the same assumption: that somewhere there's a single "property database" you connect to. There isn't. In North America alone there are roughly 500 independent MLS boards, each with its own schema, field names, and compliance rules — and a real-estate MLS integration is mostly the work of making that fragmentation look like one clean dataset to your users.
Here's the shape of a build that survives: pull from the RESO Web API (not legacy RETS), sync incrementally with delta queries, normalize every board into one internal schema, and serve images and map search from infrastructure built for it. Do that and you get a fast listing search without burning your API quota or tripping an IDX violation. Here are the eight rules that matter.

Rule 1: RESO Web API Over Legacy RETS
Two protocols connect you to a board:
- RETS — the old XML-over-HTTP standard. It makes you download massive dumps (often gigabytes) to sync, wasting storage and bandwidth.
- RESO Web API — the modern, mandated standard built on OData and REST. You run granular JSON queries and pull only what you need.
The National Association of Realtors requires RESO Web API support, so build against it. Keep a RETS bridge in your back pocket only for the occasional board that hasn't fully migrated.
Rule 2: Query With OData Parameters
The RESO Web API uses OData's $filter, $select, and $top to slice data precisely — fetch active residential listings modified since yesterday, only the fields you need:
1GET https://{mls-host}/odata/Property
2 ?$filter=ModificationTimestamp gt 2026-06-28T00:00:00Z and StandardStatus eq 'Active' and ListPrice gt 500000
3 &$select=ListingId,ListPrice,BedroomsTotal,BathroomsFull,PublicRemarks
4 &$top=100
5Authorization: Bearer {access_token}Note gt, not > — OData spells its operators out (gt, lt, eq). Selecting only the fields you render keeps payloads small.
Rule 3: Normalize Every Board to One Schema
SqFt here, LivingArea there, BuildingAreaTotal somewhere else — same number, three names. Don't build a table per board. Map everything into one internal schema aligned to the RESO Data Dictionary with adapter modules:
1// src/mls/adapters/mls-normalizer.service.ts
2import { Injectable } from '@nestjs/common';
3
4@Injectable()
5export class MlsNormalizerService {
6 normalize(raw: Record<string, any>) {
7 return {
8 listingId: raw.ListingId ?? raw.ListingID ?? raw.id,
9 price: Number(raw.ListPrice ?? raw.Price ?? raw.CurrentPrice),
10 bedrooms: Number(raw.BedroomsTotal ?? raw.Beds ?? raw.Bedrooms),
11 squareFeet: Number(raw.LivingArea ?? raw.SqFt ?? raw.BuildingAreaTotal),
12 };
13 }
14}Onboarding a new board becomes writing one adapter, not reworking your data layer.

Rule 4: Sync Incrementally With Delta Queries
This is the rule that keeps your API access alive. Never re-pull the whole dataset on a schedule — hundreds of thousands of listings daily will exhaust your quota and lock your workers. Track your last sync time and ask only for what changed:
1// runs on a worker, not in a request
2async function incrementalSync(lastSync: Date) {
3 const since = lastSync.toISOString();
4 const endpoint = `/odata/Property?$filter=ModificationTimestamp gt ${since}`;
5 const changed = await fetchFromMls(endpoint);
6 await db.propertyListings.upsertBatch(changed);
7}Run it on your background job infrastructure, and treat the MLS like any flaky upstream — timeouts, retries, and backoff, the standard third-party API reliability patterns.
Rule 5: Proxy Images at the Edge, Don't Copy Them
A listing has dozens of high-res photos. Copy every one into your own bucket and your storage bill balloons; hotlink the raw MLS URLs and you get broken images when listings change. Instead, store only the source URL and pass it through an on-demand edge proxy (Cloudflare Images, Imgix) that scales and compresses on the fly. Cheap storage, fast delivery, no stale copies.
Rule 6: Build Map Search With PostGIS
Letting users draw a search area on a map is a geospatial query, not application math. Use PostGIS: store coordinates as GEOMETRY(Point, 4326), index with GiST, and answer polygon searches in milliseconds:
1SELECT listing_id, price, coordinates
2FROM property_listings
3WHERE ST_Contains(
4 ST_GeomFromText('POLYGON((-73.93 40.7, -73.91 40.7, -73.91 40.8, -73.93 40.8, -73.93 40.7))', 4326),
5 coordinates
6);A GiST index organizes space into nested bounding boxes so the database skips everything outside the shape — the same "index, don't scan" lesson from any PostgreSQL performance work, applied to geometry.

Rule 7: Stay IDX-Compliant
Displaying listings publicly means honoring each board's IDX rules. The common ones:
- Branding — show the originating MLS logo and copyright disclaimer on listing displays.
- Attribution — credit the listing broker and agent, often at a required prominence.
- Refresh cadence — keep stored data fresh (many boards require at least every 12 hours) so nothing shown is stale.
The exact rules vary per board, so read every MLS agreement you sign — IDX non-compliance doesn't get you a warning email, it gets your feed cut.
Rule 8: MLS Integration Protocols Compared
| Legacy RETS | RESO Web API | Aggregator (Bridge, Trestle) | |
|---|---|---|---|
| Protocol | XML over HTTP | REST + OData JSON | Unified REST wrapper |
| Ingestion | Full dumps | Incremental deltas | One multi-board endpoint |
| Memory | Heavy | Light, streamable | Light (no raw storage) |
| Setup | Slow, complex parsing | Medium (OData) | Fast |
| Cost | Server maintenance | Flat infra | Per-query licensing |
If a board only speaks RETS, a RESO-aligned aggregator like Bridge Interactive or CoreLogic Trestle can normalize it to clean REST so you don't write legacy parsing yourself — at the cost of per-query licensing.
Connect via RESO, sync the deltas, normalize to one schema, and let PostGIS and an image proxy do the heavy lifting. Get those right and an MLS integration stops being a fragmented data nightmare and becomes the fast, compliant search that makes a real-estate product feel effortless — while you quietly handle 500 boards' worth of inconsistency behind the scenes.
Frequently Asked Questions
RETS is the legacy protocol — XML over HTTP that makes you download bulky data dumps to sync. The RESO Web API is the modern standard, built on OData and REST, so you run granular JSON queries and pull only what changed. The National Association of Realtors mandates RESO Web API support, so build against it; only reach for a RETS bridge if a specific older board hasn't migrated yet.
Never pull the full dataset on a schedule — hundreds of thousands of listings will blow your API quota and lock up your workers. Use incremental delta sync: store the timestamp of your last successful run and query only records with a ModificationTimestamp greater than it (OData $filter), or use the API's delta tokens. You fetch the handful that changed instead of everything that exists.
Every board names fields differently — SqFt vs LivingArea vs BuildingAreaTotal. Don't build per-board tables. Map every feed into one internal schema aligned to the RESO Data Dictionary using adapter modules that normalize incoming variations on the way in. Your application then queries one consistent shape, and onboarding a new board is writing one adapter, not reworking your database.
Use PostGIS in PostgreSQL. Store coordinates as GEOMETRY(Point, 4326), add a GiST index, and answer 'properties inside this drawn polygon' with ST_Contains in milliseconds. Doing the geometry math in application code instead is orders of magnitude slower — spatial indexes organize space into bounding boxes so the database skips everything outside your shape.
IDX (Internet Data Exchange) rules are set by each MLS board for displaying listings publicly. Common requirements: show the originating MLS logo and copyright disclaimer, credit the listing broker and agent, and refresh stored data on a schedule (often at least every 12 hours) so listings aren't stale. The exact rules vary by board, so read the agreement for each MLS you connect — non-compliance can cost you the feed.
