Developer Documentation
Platform Overview
Authentication
API Services
Overview Accounts Accounts: Associations Accounts: Metadata Accounts: Profile Appstore: Users Broker Distributions Broker Tours Consumers Consumers: Linked Agents Contacts Contacts: Activity Contacts: Export Contacts: Tags Contacts: Portal Accounts Developers: Identities Developers: Keys Developers: Authorizations Developers: Billing Summary Developers: Change History Developers: Domains Developers: News Feed Webhooks Developers: Roles Developers: Syndications Developers: Templates Developers: Usage Detail Developers: Usage Summary Devices Flexmls: Email Links Flexmls: Listing Meta Origins Flexmls: Listing Meta Translations Flexmls: Listing Meta Field List Translations Flexmls: Listing Reports Flexmls: Mapping Layers Flexmls: Mapping Shapegen IDX IDX Links Listing Carts Listing Carts: Portal/VOW Carts Incomplete Listings Incomplete Listings: Documents Incomplete Listings: Documents Metadata Incomplete Listings: Document Uploads Incomplete Listings: Floor Plans Incomplete Listings: Floor Plans Metadata Incomplete Listings: Floor Plan Uploads Incomplete Listings: Photos Incomplete Listings: Photos Metadata Incomplete Listings: Photo Uploads Incomplete Listings: Required Documents Incomplete Listings: Rooms Incomplete Listings: Tickets Incomplete Listings: Units Incomplete Listings: Videos Incomplete Listings: Videos Metadata Incomplete Listings: Virtual Tours Incomplete Listings: Virtual Tours Metadata Listings Listings: Clusters Listings: Documents Listings: Documents Metadata Listings: Document Uploads Listings: Floor Plans Listings: Floor Plans Metadata Listings: Floor Plan Uploads Listings: Historical Listings: History Listings: Hot Sheet Parameters Listings: Notes Listings: Search Parameters Listings: Open Houses Listings: Photos Listings: Photos Metadata Listings: Photo Uploads Listings: Rental Calendar Listings: Required Documents Listings: Rooms Listings: Rules Listings: Tour of Homes Listings: Tickets Listings: Units Listings: Validation Listings: Videos Listings: Videos Metadata Listings: Virtual Tours Listings: Virtual Tours Metadata Listing Meta: Custom Fields Listing Meta: Custom Field Groups Listing Meta: Field Order Listing Meta: Field Relations Listing Meta: Property Types Listing Meta: Rooms Listing Meta: Standard Fields Listing Meta: Units Registered Listings Market Statistics News Feed News Feed: Curation News Feed: Events News Feed: Groups News Feed: Metadata News Feed: Restrictions News Feed: Schedule News Feed: Settings News Feed: Templates Notifications Open Houses Overlays Overlays: Geometries Portals Portals: Listing Categories Portals: Metadata Preferences Saved Searches Saved Searches: Provided Saved Searches: Restrictions Saved Searches: Tags Search Templates: Quick Searches Search Templates: Views Search Templates: Sorts Shared Links System Info System Info: Languages System Info: Search Templates
Supporting Documentation
Examples
RESO Web API
RETS
Terms of Use

RESO Web API Quick Start Guide

This guide will walk you through the basics of the RESO Web API — searching properties, retrieving details, and fetching media — in under 20 minutes. You can follow along using curl, Python, JavaScript, Ruby, or PHP.

The RESO Web API is built on the RESO standard and uses OData query syntax. It shares the same credentials and authentication as the Spark® API, so if you have already completed the Spark API Quick Start, your credentials work here too.

  1. Prerequisites
  2. Step 1 — Get an Access Token
  3. Step 2 — Search Properties
  4. Step 3 — Get a Specific Property
  5. Step 4 — Expand Media (Photos)
 

Prerequisites

You need the same API credentials used for the Spark API. Register as a developer at sparkplatform.com to receive your client_id, client_secret, and optionally a pre-provisioned access_token.

The RESO Web API v3 base URL is:

https://replication.sparkapi.com/Version/3/Reso/OData/

All requests must be made over HTTPS.

 

Step 1 — Get an Access Token

Authentication is identical to the Spark API — see the Spark API Quick Start, Step 1 for the full walkthrough. The short version:

 
curl
curl -X POST "https://sparkapi.com/v1/oauth2/grant" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "[client_id]",
    "client_secret": "[client_secret]",
    "grant_type": "authorization_code",
    "code": "[code]",
    "redirect_uri": "[redirect_uri]"
  }'
 
Python
import requests

response = requests.post(
    "https://sparkapi.com/v1/oauth2/grant",
    json={
        "client_id": "[client_id]",
        "client_secret": "[client_secret]",
        "grant_type": "authorization_code",
        "code": "[code]",
        "redirect_uri": "[redirect_uri]"
    }
)

access_token = response.json()["access_token"]
print("Access token:", access_token)
 
JavaScript (Node.js)
const response = await fetch("https://sparkapi.com/v1/oauth2/grant", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    client_id: "[client_id]",
    client_secret: "[client_secret]",
    grant_type: "authorization_code",
    code: "[code]",
    redirect_uri: "[redirect_uri]"
  })
});

const { access_token } = await response.json();
console.log("Access token:", access_token);
 
Ruby
require "net/http"
require "json"

uri = URI("https://sparkapi.com/v1/oauth2/grant")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path, "Content-Type" => "application/json")
request.body = JSON.generate(
  client_id: "[client_id]",
  client_secret: "[client_secret]",
  grant_type: "authorization_code",
  code: "[code]",
  redirect_uri: "[redirect_uri]"
)

access_token = JSON.parse(http.request(request).body)["access_token"]
puts "Access token: #{access_token}"
 
PHP
<?php
$ch = curl_init("https://sparkapi.com/v1/oauth2/grant");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "client_id"     => "[client_id]",
    "client_secret" => "[client_secret]",
    "grant_type"    => "authorization_code",
    "code"          => "[code]",
    "redirect_uri"  => "[redirect_uri]"
]));

$access_token = json_decode(curl_exec($ch), true)["access_token"];
echo "Access token: " . $access_token;
 

Step 2 — Search Properties

Query the Property resource using OData's $filter parameter. The example below returns the first 5 active listings in Portland with at least 3 bedrooms.

The Authorization header uses the same OAuth scheme as the Spark API.

 
curl
curl "https://replication.sparkapi.com/Version/3/Reso/OData/Property?\$filter=City eq 'Portland' and BedroomsTotal ge 3 and StandardStatus eq 'Active'&\$top=5&\$select=ListingKey,StreetName,City,ListPrice,BedroomsTotal" \
  -H "Authorization: OAuth [access_token]"
 
Python
import requests

access_token = "your_access_token_here"
base_url = "https://replication.sparkapi.com/Version/3/Reso/OData"

response = requests.get(
    f"{base_url}/Property",
    headers={"Authorization": f"OAuth {access_token}"},
    params={
        "$filter": "City eq 'Portland' and BedroomsTotal ge 3 and StandardStatus eq 'Active'",
        "$top": 5,
        "$select": "ListingKey,StreetName,City,ListPrice,BedroomsTotal"
    }
)

properties = response.json()["value"]
for prop in properties:
    print(prop["ListingKey"], prop["StreetName"], prop["City"], prop["ListPrice"])
 
JavaScript (Node.js)
const accessToken = "your_access_token_here";
const baseUrl = "https://replication.sparkapi.com/Version/3/Reso/OData";

const params = new URLSearchParams({
  $filter: "City eq 'Portland' and BedroomsTotal ge 3 and StandardStatus eq 'Active'",
  $top: 5,
  $select: "ListingKey,StreetName,City,ListPrice,BedroomsTotal"
});

const response = await fetch(`${baseUrl}/Property?${params}`, {
  headers: { Authorization: `OAuth ${accessToken}` }
});

const { value: properties } = await response.json();
properties.forEach(p => console.log(p.ListingKey, p.StreetName, p.City, p.ListPrice));
 
Ruby
require "net/http"
require "json"

access_token = "your_access_token_here"
uri = URI("https://replication.sparkapi.com/Version/3/Reso/OData/Property")
uri.query = URI.encode_www_form(
  "$filter" => "City eq 'Portland' and BedroomsTotal ge 3 and StandardStatus eq 'Active'",
  "$top"    => 5,
  "$select" => "ListingKey,StreetName,City,ListPrice,BedroomsTotal"
)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri, "Authorization" => "OAuth #{access_token}")

properties = JSON.parse(http.request(request).body)["value"]
properties.each do |p|
  puts "#{p['ListingKey']} #{p['StreetName']}, #{p['City']} $#{p['ListPrice']}"
end
 
PHP
<?php
$access_token = "your_access_token_here";
$query = http_build_query([
    "\$filter" => "City eq 'Portland' and BedroomsTotal ge 3 and StandardStatus eq 'Active'",
    "\$top"    => 5,
    "\$select" => "ListingKey,StreetName,City,ListPrice,BedroomsTotal"
]);

$ch = curl_init("https://replication.sparkapi.com/Version/3/Reso/OData/Property?" . $query);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: OAuth $access_token"]);

$data = json_decode(curl_exec($ch), true);
foreach ($data["value"] as $prop) {
    echo $prop["ListingKey"] . " " . $prop["StreetName"] . ", " . $prop["City"] . " $" . $prop["ListPrice"] . "\n";
}
 

A successful response looks like:

{
  "@odata.context": "https://replication.sparkapi.com/Version/3/Reso/OData/$metadata#Property",
  "value": [
    {
      "ListingKey": "20100000000000000000000000",
      "StreetName": "Main St",
      "City": "Portland",
      "ListPrice": 450000,
      "BedroomsTotal": 3
    }
  ]
}

For a full list of OData query options, see RESO Request Parameters.

 

Step 3 — Get a Specific Property

Retrieve full details for a single property using its ListingKey.

 
curl
curl "https://replication.sparkapi.com/Version/3/Reso/OData/Property('20100000000000000000000000')" \
  -H "Authorization: OAuth [access_token]"
 
Python
listing_key = "20100000000000000000000000"

response = requests.get(
    f"{base_url}/Property('{listing_key}')",
    headers={"Authorization": f"OAuth {access_token}"}
)

prop = response.json()
print(f"{prop['StreetName']}, {prop['City']} — ${prop['ListPrice']:,}")
 
JavaScript (Node.js)
const listingKey = "20100000000000000000000000";

const response = await fetch(`${baseUrl}/Property('${listingKey}')`, {
  headers: { Authorization: `OAuth ${accessToken}` }
});

const prop = await response.json();
console.log(`${prop.StreetName}, ${prop.City} — $${prop.ListPrice}`);
 
Ruby
listing_key = "20100000000000000000000000"
uri = URI("https://replication.sparkapi.com/Version/3/Reso/OData/Property('#{listing_key}')")

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri, "Authorization" => "OAuth #{access_token}")

prop = JSON.parse(http.request(request).body)
puts "#{prop['StreetName']}, #{prop['City']} — $#{prop['ListPrice']}"
 
PHP
<?php
$listing_key = "20100000000000000000000000";

$ch = curl_init("https://replication.sparkapi.com/Version/3/Reso/OData/Property('$listing_key')");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: OAuth $access_token"]);

$prop = json_decode(curl_exec($ch), true);
echo "{$prop['StreetName']}, {$prop['City']} — \${$prop['ListPrice']}";
 

Step 4 — Expand Media (Photos)

Use the $expand parameter to include related Media records (photos) directly in the property response, avoiding a second request.

 
curl
curl "https://replication.sparkapi.com/Version/3/Reso/OData/Property('20100000000000000000000000')?\$expand=Media" \
  -H "Authorization: OAuth [access_token]"
 
Python
response = requests.get(
    f"{base_url}/Property('{listing_key}')",
    headers={"Authorization": f"OAuth {access_token}"},
    params={"$expand": "Media"}
)

prop = response.json()
for media in prop.get("Media", []):
    print(media.get("MediaURL"))  # URL to the photo
 
JavaScript (Node.js)
const response = await fetch(
  `${baseUrl}/Property('${listingKey}')?$expand=Media`,
  { headers: { Authorization: `OAuth ${accessToken}` } }
);

const prop = await response.json();
(prop.Media || []).forEach(m => console.log(m.MediaURL));
 
Ruby
uri = URI("https://replication.sparkapi.com/Version/3/Reso/OData/Property('#{listing_key}')")
uri.query = URI.encode_www_form("$expand" => "Media")

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri, "Authorization" => "OAuth #{access_token}")

prop = JSON.parse(http.request(request).body)
(prop["Media"] || []).each { |m| puts m["MediaURL"] }
 
PHP
<?php
$query = http_build_query(["\$expand" => "Media"]);

$ch = curl_init("https://replication.sparkapi.com/Version/3/Reso/OData/Property('$listing_key')?" . $query);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: OAuth $access_token"]);

$prop = json_decode(curl_exec($ch), true);
foreach ($prop["Media"] ?? [] as $media) {
    echo $media["MediaURL"] . "\n";
}
 

A successful response looks like:

{
  "@odata.context": "https://replication.sparkapi.com/Version/3/Reso/OData/$metadata#Property/$entity",
  "ListingKey": "20100000000000000000000000",
  "StreetName": "Main St",
  "City": "Portland",
  "ListPrice": 450000,
  "Media": [
    {
      "MediaKey": "media_id_here",
      "MediaURL": "https://cdn.sparkapi.com/reso/media/photo1.jpg",
      "Order": 0,
      "MediaCategory": "Photo"
    }
  ]
}
 

Next Steps