RESO API
Ruby wrapper for easy interaction with a RESO Web API compliant server.
This document does not documentation for the RESO Web API. More information about the RESO Web API standard can be found on RESO's website.
Installation
Add this line to your application's Gemfile:
gem 'reso_api'
And then execute:
$ bundle
Or install it yourself as:
$ gem install reso_api
Usage
Authentication and Access
This gem supports two types of authentication:
- OAuth2
- Access Token
OAuth2
To set up an API client using OAuth2 authentication, you need four pieces of information:
- Client ID
- Client Secret
- Authentication URL
- Base URL
- Scope
Often, the base URL ends with /odata
, and the authentication URL often ends with /token
.
Scope defaults to "api" and only needs to be included if it is "OData" or something else.
You pass these four pieces of information to create an instance of an API client:
client = RESO::API::Client.new(client_id: client_id, client_secret: client_secret, auth_url: auth_url, base_url: base_url, scope: scope)
When calling API endpoints using the initialized client, it will automatically fetch and manage access and authentication tokens transparently in the background.
Access Token
Some systems, like MLSGRID and Spark/Flexmls provides a persistent Access Token. In these cases, you need these two pieces of information to set up an API client:
- Access Token
- Base API endpoint
You pass these two pieces of information to create an instance of an API client:
client = RESO::API::Client.new(access_token: access_token, base_url: base_url)
Resources
Supported Resources
- Media
- Member
- Office
- Property
Retrieving Metadata
You can fetch metadata for supported resources:
client.metadata
The response will be an EDMX xml schema document that matches the RESO Data Dictionary standard.
Listing Requests
The simplest query is simply to call properties
on the initialized client:
client.properties
The API will return a JSON response with an array of listing JSON objects.
Getting a single listing
You can look up a single listing by sending a query with the listing's unique id (ListingKey).
client.property('3yd-BINDER-5508272')
The response will be a single listing JSON object.
Running queries
$filter
You can use $filter
to send simple and complex queries to get a subset of listings depending on your particular use case.
To use $filter
, pass it an expression in the format FieldName operator Value
. For example, this query will return listings where the StandardStatus
field equals (eq
) the value 'Active'
:
client.properties(filter: "StandardStatus eq 'Active'")
You can combine expressions together with or and and to perform more complex queries.
client.properties(filter: "StandardStatus eq 'Active' and BrokerName eq 'Doe Brokerage'")
RESO Web API is built on the OData standard, but only requires compliant servers to support a subset of queries:
Operator | Description | Example |
---|---|---|
eq |
Equals | StandardStatus eq 'Active' |
ne |
Not equals | StandardStatus ne 'Active' |
ge |
Greater than or equals | ListPrice ge 100000 |
gt |
Greater than | ListPrice gt 100000 |
le |
Less than or equals | ListPrice le 100000 |
lt |
Less than | ListPrice lt 100000 |
$select
Instead of returning all of the listing fields in the response, you can use $select
to only return specific fields you need.
client.properties(select: "ListingKey")
client.properties(select: "ListingKey", filter: "StandardStatus eq 'Active'")
You can specify multiple fields to return by adding a "," between the fields in the $select
parameter:
client.properties(select: "ListingKey,StandardStatus")
$orderby
You can order the results by a field using $orderby
:
client.properties(orderby: "City desc")
$expand
$expand in oData is meant to join two resources together. For the Syndication API this means you can bring in photos to any property query.
client.properties(expand: "Media")
$ignorenulls
For servers that support it, $ignorenulls
omits empty keys from the response to reduce data size.
client.properties(ignorenulls: true)
Automatically iterate over all results
By passing a block to Media, Member, Office, and Property resource calls, subsequent paginated calls will automatically be made until the whole result set has been traversed. The block provided is executed for each returned object hash. The batch
option can be used to return the full array of results.
Here are a couple of examples of how that can be used:
client.properties(filter: "StandardStatus eq 'Active'") do |hash|
puts "#{hash['ListingKey']} – #{hash['UnparsedAddress']}"
end
client.properties(filter: "StandardStatus eq 'Active'") do |hash|
Listing.create(listing_key: hash['ListingKey'], data: hash)
end
client.properties(filter: "StandardStatus eq 'Active'", batch: true) do |results|
Listing.insert_all(results) # Perform some batch operation
end
Manually iterate over all results
The default number of results returned is 100. You can override the default limit using the $top
parameter. The higher the number specific for $top
, the longer the API response will take, and pay attention to that different services does enforce a cap for number of records returned.
You can paginate through multiple sets of results using $skip
, this can help you process multiple records quickly. In addition, if you use $select
to target only certain fields, the API response time will be faster.
The following example API request will get the next 200 records starting at the 500th record:
client.properties(select: "ListingKey", top: 200, skip: 500)
As you paginate through very large datasets, you might notice that the higher you set $skip
the longer it takes for the API to return results. For those use cases, you should use $skiptoken
to process very large datasets quickly.
Using $skiptoken for large datasets
$skiptoken
can be used in combination with $orderby
to process large datasets. To illustrate $skiptoken
, we will use the following request to get 5 records ordered by ListingKey
:
client.properties(top: 5, orderby: "ListingKey")
An extract of the result could be:
{
"@odata.context": "http://some.server.com/odata/$metadata#Property(ListingKey)",
"value": [
{
"ListingKey": "3yd-AAABORMI-2813483"
},
{
"ListingKey": "3yd-AAABORMI-2910696"
},
{
"ListingKey": "3yd-AAABORMI-3101621"
},
{
"ListingKey": "3yd-AAABORMI-3101967"
},
{
"ListingKey": "3yd-AAABORMI-3200394"
}
]
}
To get the next set of records, you would then use the value of the ListingKey field for the last record in the current set (3yd-AAABORMI-3200394) as your value for $skiptoken
:
client.properties(top: 5, orderby: "ListingKey", skiptoken: "3yd-AAABORMI-3200394")
$skiptoken
allows you to process large datasets from the API in a sequence without sacrificing performance. The drawback is that you cannot "skip" to a random page, for that you must use the regular $skip
parameter.
Compatibility
This gem should work with any RESO Web API compliant service, but these are those that have been confirmed to work.
If you use this gem to connect to another service or MLS, please submit a pull request with that service added in alphabetical order in this list.
Acknowledgment
The inspiration for this gem and the outline and examples in this README is based on the ListHub API.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/arcticleo/reso_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
Code of Conduct
Everyone interacting in the RESO API project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.