RESO Client Library
A Rust client library for RESO Web API servers using OData 4.0.
Features
- 🔍 Fluent query builder for OData queries
- 🔐 OAuth bearer token authentication
- 📊 Support for filters, ordering, pagination, and field selection
- 🔢 Count-only queries for efficient record counting ⚠️
- 🗂️ Optional dataset ID path support
- 📖 Metadata retrieval
- 🔄 Replication endpoint support for bulk data transfer (up to 2000 records/request) ⚠️
- ⚡ Async/await with tokio
⚠️ Some features not supported by the RESO Web API / actris_ref test server.
Installation via Github
Pending Cargo crate publication, use the Github repository to install - add to your Cargo.toml:
[]
# Import the RESO client from GitHub
= { = "https://github.com/jeremeybingham/reso_client" }
Configuration
Environment Variables
The client reads configuration from environment variables:
| Variable | Required | Description | Example |
|---|---|---|---|
RESO_BASE_URL |
Yes | Base URL of the RESO Web API server | https://api.bridgedataoutput.com/api/v2/OData |
RESO_TOKEN |
Yes | OAuth bearer token for authentication | your-token-here |
RESO_DATASET_ID |
No | Dataset identifier (see below) | actris_ref |
RESO_TIMEOUT |
No | HTTP timeout in seconds (default: 30) | 60 |
Create a .env file:
RESO_BASE_URL=https://api.bridgedataoutput.com/api/v2/OData
RESO_TOKEN=your-token-here
RESO_DATASET_ID=actris_ref
RESO_TIMEOUT=30
Dataset ID Explained
Configured to handle the Bridges/ACTRIS RESO Web API Reference Server, which uses a dataset identifier in the URL path. The dataset ID is inserted between the base URL and the resource name.
Without dataset ID:
https://api.mls.com/OData/Property
With dataset ID:
https://api.mls.com/OData/actris_ref/Property
https://api.mls.com/OData/actris_ref/$metadata
When to use:
- Required: If your RESO provider's API documentation shows URLs with a dataset/database identifier
- Optional: If your provider uses a simple base URL structure
You can set it via environment variable or programmatically:
// Via environment
let client = from_env?;
// Via builder
let config = new
.with_dataset_id;
let client = with_config?;
Testing
Running Tests
The library includes comprehensive test coverage with both unit and integration tests:
# Run all tests (unit + integration)
# Run only unit tests (in src/ modules)
# Run only integration tests (in tests/ directory)
# Run specific test file
Test Organization:
- Unit tests: Internal tests for private functions and implementation details
- Integration tests: Public API tests in
tests/directorytests/queries_tests.rs- Comprehensive query building and URL generation tests
Examples
All examples include detailed comments, error handling, and work with the RESO Web API reference server / actris_ref unless otherwise noted. The library includes a comprehensive suite of examples in the examples directory demonstrating all major functionality. Assuming you've set your .env variables correctly, you can run any example with:
Available Examples
Basic Usage:
test_connectivity- Test basic API connectivity and authenticationtest_property- Property resource queries with filtering and field selectiontest_member- Query Member resource for agent/broker informationtest_metadata- Fetch and parse OData metadata documentstest_core_queries- Tests the "Core Queries" specified in the RESO Web API reference documentation
Query Features:
test_filters- OData filter syntax (comparison, logical operators, string functions)test_select- Field selection and projection to optimize response size
Analysis Examples:
analyze_property_fields- Analyze field usage across 200 active listings to identify which fields are most populated; generatesproperty_field_analysis_report.jsonwith recommended field sets (minimal, standard, comprehensive)analyze_active_listings- Statistical analysis of 200 active residential listings including price analysis, property type distribution, geographic distribution, bedroom/bathroom statistics, size metrics, and photo counts
⚠️ Server-Specific (currently untestested, requires server support):
The $count, $apply, and $expand features are not supported by the RESO Web API test server / actris_ref. The Replication endpoint is also not supported by default on actris_ref. Examples using these features will fail with 404 or 401 errors.
- ⚠️
test_replication- Replication endpoint for bulk data transfer (up to 2000 records/request) - ⚠️
test_count_only- Efficient count-only queries using/$countendpoint - ⚠️
test_pagination_nextlink- Server-side pagination with@odata.nextLink - ⚠️
test_apply- OData aggregation with$applyparameter - ⚠️
test_expand- Navigation property expansion with$expandparameter
Quick Start
Standard Queries
use ;
async
Replication Queries (Bulk Data Transfer)
use ;
async
Usage Examples
Basic Query
let query = new
.top
.build?;
let results = client.execute.await?;
Filtering
Use OData 4.0 filter syntax:
// Simple equality
let query = new
.filter
.build?;
// Comparison operators
let query = new
.filter
.build?;
// String functions
let query = new
.filter
.build?;
// Date comparison
let query = new
.filter
.build?;
// Complex expressions
let query = new
.filter
.build?;
Field Selection
let query = new
.select
.top
.build?;
Sorting
let query = new
.order_by
.top
.build?;
Pagination
// First page
let query = new
.top
.build?;
// Second page
let query = new
.skip
.top
.build?;
Getting Total Count
let query = new
.filter
.with_count
.top
.build?;
let results = client.execute.await?;
// Access the count
if let Some = results.as_u64
Count-Only Queries
Efficiently get just the count without fetching records:
let query = new
.filter
.count // Returns just the count via /$count endpoint
.build?;
let results = client.execute.await?;
let count = results.as_u64.unwrap_or;
println!;
OData Aggregation (when supported)
⚠️ Server Compatibility, NOT supported by the RESO Web API reference server / actris_ref Note: The apply() method requires server support for OData v4.0 Aggregation Extensions. Not all RESO servers support this feature.
// Group by field with aggregation (if server supports $apply)
let query = new
.apply
.build?;
let results = client.execute.await?;
If your server doesn't support $apply, use multiple filtered queries instead:
⚠️ This is the method supported by the RESO Web API reference server / actris_ref
// Workaround: Use $filter for counts by category
let statuses = ;
for status in statuses
Fetching Metadata
Retrieve the OData metadata document:
let metadata_xml = client.fetch_metadata.await?;
println!;
Replication Queries
The replication endpoint is designed for bulk data transfer and synchronization of large datasets (>10,000 records). It supports up to 2000 records per request (vs 200 for standard queries) and uses header-based pagination.
Important notes:
- Requires MLS authorization
- Results are ordered oldest to newest by default
- No support for
$skip,$orderby,$apply, or count options - Use
$selectto reduce payload size and improve performance
use ;
// Build a replication query
let query = new
.filter
.select
.top // Maximum: 2000
.build?;
// Execute the query
let response = client.execute_replication.await?;
println!;
// Process records
for record in &response.records
// Continue with next link if more records available
if let Some = response.next_link
Fetching all records with pagination:
let mut query = new
.top
.build?;
let mut response = client.execute_replication.await?;
let mut all_records = response.records;
// Continue fetching while next link is available
while let Some = response.next_link
println!;
OData Response Structure
The RESO Web API returns responses in OData format:
Key fields:
value: Array of records matching your query@odata.count: Total count (only whenwith_count()is used)@odata.nextLink: URL for next page (for server-side pagination)
Access records:
let results = client.execute.await?;
if let Some = results.as_array
Error Handling
use ;
match client.execute.await
Advanced Configuration
Custom Timeout
use Duration;
let config = new
.with_timeout;
let client = with_config?;
Manual Configuration
let config = new
.with_dataset_id
.with_timeout;
let client = with_config?;
OData Filter Reference
Common OData 4.0 operators:
| Operator | Description | Example |
|---|---|---|
eq |
Equals | City eq 'Austin' |
ne |
Not equals | Status ne 'Closed' |
gt |
Greater than | ListPrice gt 500000 |
ge |
Greater than or equal | BedroomsTotal ge 3 |
lt |
Less than | ListPrice lt 1000000 |
le |
Less than or equal | BedroomsTotal le 5 |
and |
Logical AND | City eq 'Austin' and ListPrice gt 500000 |
or |
Logical OR | City eq 'Austin' or City eq 'Manor' |
not |
Logical NOT | not (City eq 'Austin') |
String functions:
startswith(field, 'value')endswith(field, 'value')contains(field, 'value')
Date functions:
year(field) eq 2025month(field) eq 6day(field) eq 15
For complete OData 4.0 filter syntax, see: OData URL Conventions
License
Licensed under the terms of the MIT license. See the file:
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)