A comprehensive Python wrapper for the Wasatch Front Regional MLS (WFRMLS) API, providing easy access to all RESO-certified endpoints.
property.get_property() now returns one normalized response shape: a single property dictionary. If the upstream API responds with an OData wrapper such as {"value": [...]}, the library unwraps it internally so callers can always read fields like ParcelNumber directly from the returned property object.
Media, History, and Green Verification endpoints are currently unavailable due to server-side issues (504 Gateway Timeouts and missing entity types). These features have been temporarily disabled until the server issues are resolved.
from wfrmls import WFRMLSClient
# Initialize client with bearer token
client = WFRMLSClient(bearer_token="your_bearer_token")
# Or use environment variable WFRMLS_BEARER_TOKEN
client = WFRMLSClient()
# Get active properties
properties = client.property.get_properties(
top=10,
filter_query="StandardStatus eq 'Active'"
)
# Get property details
property_detail = client.property.get_property("12345678")
print(property_detail["ParcelNumber"])
# Get member information
members = client.member.get_active_members(top=10)
# Get office information
offices = client.office.get_active_offices(top=10)pip install wfrmlsCreate a .env file in your project root:
WFRMLS_BEARER_TOKEN=your_bearer_token_here- Visit the Vendor Dashboard
- Login to your account
- Navigate to Service Details to retrieve your bearer token
- Property - Real estate listings and property data
- Member - Real estate agent information
- Office - Brokerage and office details
- OpenHouse - Open house schedules and events
# Property operations
client.property.get_properties()
client.property.get_property(listing_id)
client.property.search_properties_by_radius(lat, lng, radius)
# Member (agent) operations
client.member.get_members()
client.member.get_member(member_id)
# Office operations
client.office.get_offices()
client.office.get_office(office_id)
# Open house operations
client.openhouse.get_open_houses()
client.openhouse.get_open_house(openhouse_id)# Field selection
properties = client.property.get_properties(
select=["ListingId", "ListPrice", "StandardStatus"],
top=50
)
# Complex filtering
properties = client.property.get_properties(
filter_query="ListPrice ge 200000 and ListPrice le 500000 and StandardStatus eq 'Active'",
orderby="ListPrice desc"
)
# Include related data
properties = client.property.get_properties(
expand=["Media", "Member"],
top=25
)# Search within radius (miles)
properties = client.property.search_properties_by_radius(
latitude=40.7608, # Salt Lake City
longitude=-111.8910,
radius_miles=10,
additional_filters="StandardStatus eq 'Active'"
)
# Search within polygon area
polygon = [
{"lat": 40.7608, "lng": -111.8910},
{"lat": 40.7708, "lng": -111.8810},
{"lat": 40.7508, "lng": -111.8710},
{"lat": 40.7608, "lng": -111.8910} # Close polygon
]
properties = client.property.search_properties_by_polygon(
polygon_coordinates=polygon,
additional_filters="PropertyType eq 'Residential'"
)from datetime import datetime, timedelta
# Get incremental updates (recommended every 15 minutes)
cutoff_time = datetime.utcnow() - timedelta(minutes=15)
updates = client.property.get_properties(
filter_query=f"ModificationTimestamp gt {cutoff_time.isoformat()}Z"
)
# Track deletions for data integrity
deleted_records = client.deleted.get_deleted(
filter_query="ResourceName eq 'Property'"
)The client follows a modular architecture with service separation:
WFRMLSClient
βββ property # Property listings
βββ member # Real estate agents
βββ office # Brokerages/offices
βββ openhouse # Open house events
βββ lookup # Lookup tables
βββ adu # Accessory Dwelling Units
βββ deleted # Deletion tracking
βββ data_system # API metadata
Note: Media, History, and Green Verification clients are currently disabled due to server-side issues.
from wfrmls.exceptions import (
WFRMLSError,
AuthenticationError,
NotFoundError,
RateLimitError
)
try:
property_data = client.property.get_property("12345678")
print(property_data["ParcelNumber"])
except NotFoundError:
print("Property not found")
except RateLimitError:
print("Rate limit exceeded - wait before retrying")
except AuthenticationError:
print("Invalid bearer token")
except WFRMLSError as e:
print(f"API error: {e}")client.property.get_property(listing_id) returns a single property dictionary, not an OData wrapper. Fields such as ParcelNumber, ListPrice, and UnparsedAddress are top-level keys on the returned object. Missing listings raise NotFoundError.
The API supports Utah's unique grid address system:
# Standard address: "123 Main Street"
# Grid address: "1300 E 9400 S"
# Grid addresses are automatically detected and handled
properties = client.property.get_properties(
filter_query="StreetName eq '9400 S'"
)- 200 records per request maximum
- 15-minute recommended update frequency for data sync
- Use NextLink pagination for large datasets (more efficient than $skip)
# Clone repository
git clone https://github.com/theperrygroup/wfrmls.git
cd wfrmls
# Create virtual environment
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
# Install development dependencies
pip install -e .[dev]# Run tests with coverage
pytest --cov=wfrmls --cov-report=html
# Run specific test file
pytest tests/test_property.py
# Run with verbose output
pytest -v# Format code
black wfrmls tests
isort wfrmls tests
# Lint code
flake8 wfrmls tests
pylint wfrmls
# Type checking
mypy wfrmls- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow the style guide in
STYLE_GUIDE.md - Ensure 100% test coverage
- Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
For API access issues, contact UtahRealEstate.com support. For library issues, open an issue in this repository.