Skip to main content

Python wrapper for Wasatch Front Regional MLS API

Project description

WFRMLS Python Client

A comprehensive Python wrapper for the Wasatch Front Regional MLS (WFRMLS) API, providing easy access to all RESO-certified endpoints.

Release Note

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.

⚠️ Important Notice

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.

🚀 Quick Start

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)

📦 Installation

pip install wfrmls

🔧 Setup

Environment Variables

Create a .env file in your project root:

WFRMLS_BEARER_TOKEN=your_bearer_token_here

Getting Your Bearer Token

  1. Visit the Vendor Dashboard
  2. Login to your account
  3. Navigate to Service Details to retrieve your bearer token

📚 API Reference

Core Resources

  • Property - Real estate listings and property data
  • Member - Real estate agent information
  • Office - Brokerage and office details
  • OpenHouse - Open house schedules and events

Service Clients

# 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)

🔍 Advanced Features

OData Query Support

# 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
)

Geolocation Search

# 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'"
)

Data Synchronization

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'"
)

🏗️ Architecture

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.

⚠️ Error Handling

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.

📊 Utah Grid Address System

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'"
)

🚦 Rate Limits

  • 200 records per request maximum
  • 15-minute recommended update frequency for data sync
  • Use NextLink pagination for large datasets (more efficient than $skip)

🧪 Development

Setup Development Environment

# 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]

Running Tests

# 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

Code Quality

# Format code
black wfrmls tests
isort wfrmls tests

# Lint code
flake8 wfrmls tests
pylint wfrmls

# Type checking
mypy wfrmls

📝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Follow the style guide in STYLE_GUIDE.md
  4. Ensure 100% test coverage
  5. Commit changes (git commit -m 'Add amazing feature')
  6. Push to branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🔗 Links

🆘 Support

For API access issues, contact UtahRealEstate.com support. For library issues, open an issue in this repository.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wfrmls-1.3.10.tar.gz (75.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wfrmls-1.3.10-py3-none-any.whl (65.0 kB view details)

Uploaded Python 3

File details

Details for the file wfrmls-1.3.10.tar.gz.

File metadata

  • Download URL: wfrmls-1.3.10.tar.gz
  • Upload date:
  • Size: 75.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for wfrmls-1.3.10.tar.gz
Algorithm Hash digest
SHA256 b6c00956665988169b86c2f6073f43868bd4db08916e963e34a89e57afd17b1b
MD5 39f29e0dabbc64f870208f4acb403c9b
BLAKE2b-256 3a95f4babaebee643d6a56ad36b77260082c0fadd1f074a87d352b0af6e01b88

See more details on using hashes here.

File details

Details for the file wfrmls-1.3.10-py3-none-any.whl.

File metadata

  • Download URL: wfrmls-1.3.10-py3-none-any.whl
  • Upload date:
  • Size: 65.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for wfrmls-1.3.10-py3-none-any.whl
Algorithm Hash digest
SHA256 1c042e5ebc45544a4abc7098d89ef45f3c22453518cff2484616d2db5cef26fe
MD5 5d3b894edea16df2121b1b293f1bb4b4
BLAKE2b-256 6f4219766c5ac88f6190efa8cb6d3238b3f2fcf5bdd77ddcadded2a29a08344b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page