IMS API Documentation
RESTful API for accessing property, member, and office data with OData query support
Overview
The IMS API provides programmatic access to Multiple Listing Service (MLS) data including properties, members, offices, and database information. The API follows RESTful principles and supports OData query parameters for flexible data retrieval.
Key Features
- RESTful Design - Simple, predictable resource URLs
- OData Support - Powerful querying with $filter, $select, $orderby, $top, and $skip
- JWT Authentication - Secure token-based authentication
- JSON Responses - Standard JSON format for all responses
- Rate Limiting - Built-in request tracking and limits
Base URL
https://ims-api.restats.com
Quick Start
- Obtain your API credentials (client_id and client_secret)
- Generate an access token using the /authorization/token endpoint
- Make requests to /v1/{resource} endpoints with your token
- Parse the JSON response
Authentication
The IMS API uses JWT (JSON Web Token) Bearer authentication. You must include a valid access token in the Authorization header of all API requests.
Getting an Access Token
To obtain an access token, make a POST request to the token endpoint with your credentials:
Endpoint
| Method | Endpoint | Description |
|---|---|---|
| POST | /authorization/token |
Generate access token |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id |
string | Yes | Your API credential ID |
client_secret |
string | Yes | Your API credential password |
scope |
string | Yes | Must be "api" |
grant_type |
string | Yes | Must be "client_credentials" |
Example Request
curl -X POST https://ims-api.restats.com/authorization/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=api" \
-d "grant_type=client_credentials"
Success Response (200 OK)
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 36000,
"scope": "api"
}
Using the Access Token
Include the access token in the Authorization header of all API requests:
Authorization: Bearer YOUR_ACCESS_TOKEN
Token Expiration
[!] Token Lifetime: Access tokens expire after 150 minutes of inactivity.
Rate Limits
The API enforces rate limits and request quotas based on your account type. Contact your administrator for specific limits applicable to your credentials.
Endpoints
Token Generation
| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
| POST | /authorization/token |
No | Generate JWT access token |
Data Retrieval
| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
| GET | /v1/Property |
Yes | Retrieve property listings |
| GET | /v1/Member |
Yes | Retrieve member (agent) data |
| GET | /v1/Office |
Yes | Retrieve office data |
| GET | /v1/Database |
Yes | Retrieve available databases |
Request Format
GET /v1/{resource}?$filter={filter}&$select={fields}&$top={limit} HTTP/1.1
Host: ims-api.restats.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Resources
The API provides access to four main resources. Each resource returns data from specific database tables.
Property Resource
Access property listing data including prices, addresses, and related agent/office information.
Endpoint: GET /v1/Property
Available Fields
| Field | Type | Description |
|---|---|---|
OriginatingSystemName |
string | Required in $filter. Specifies which database to query. Not a database column - use only in $filter parameter |
ListingId |
string | The well known identifier for the listing (Primary Key) |
UnparsedAddress |
string | The full street address of the property in an unparsed format |
ListPrice |
number | The current price of the property as determined by the seller and the seller's broker |
ClosePrice |
number | The amount of money paid by the purchaser to the seller for the property under the agreement |
CloseDate |
date | The date the purchase agreement was fulfilled or lease requirements were met |
ListAgentMlsId |
string | The local, well-known identifier for the listing agent |
BuyerAgentMlsId |
string | The local, well-known identifier for the buyer agent |
ListOfficeMlsId |
string | The local, well-known identifier for the listing office |
BuyerOfficeMlsId |
string | The local, well-known identifier for the buyer office |
ListOfficeEmail |
string | The email address of the listing office |
BuyerOfficeEmail |
string | The email address of the buyer's office |
ModificationTimestamp |
timestamp | Date/time the property record was last modified |
Member Resource
Access agent and member information including contact details and office affiliations.
Endpoint: GET /v1/Member
Available Fields
| Field | Type | Description |
|---|---|---|
OriginatingSystemName |
string | Required in $filter. Specifies which database to query. Not a database column - use only in $filter parameter |
MemberFullName |
string | The full name of the Member. (First Middle Last) or a alternate full name (Primary Key) |
MemberEmail |
string | The email address of the Member |
MemberMlsId |
string | The local, well-known identifier for the member (Primary Key) |
OfficeMlsId |
string | The local, well-known identifier for the associated office |
OfficeName |
string | The legal name of the brokerage |
MemberMobilePhone |
string | Member's mobile phone number (format: ###-###-####) |
MemberPreferredPhone |
string | Member's preferred contact phone number (format: ###-###-####) |
MemberDirectPhone |
string | Member's direct phone line (format: ###-###-####) |
MemberLicenseNumber |
string | The real estate license number of the member |
MemberStateLicense |
string | The license of the Member. Separate multiple licenses with a comma and space |
ModificationTimestamp |
timestamp | Date/time the roster (member or office) record was last modified |
Office Resource
Access real estate office information including addresses and contact details.
Endpoint: GET /v1/Office
Available Fields
| Field | Type | Description |
|---|---|---|
OriginatingSystemName |
string | Required in $filter. Specifies which database to query. Not a database column - use only in $filter parameter |
OfficeMlsId |
string | The local, well-known identifier for the office |
OfficeName |
string | The legal name of the brokerage (Primary Key) |
OfficeAddress |
string | The street number, direction, name and suffix of the office (Primary Key) |
OfficePostalCode |
string | The postal code of the office |
OfficeCity |
string | The city of the office |
OfficePhone |
string | Office phone number (format: ###-###-####) |
OfficeBrokerKey |
string | The MemberKey of the responsible/owning broker (foreign key to Member resource) |
OfficeBrokerMlsId |
string | The MemberMlsId of the responsible/owning broker |
OfficeManagerKey |
string | The lead Office Manager for the given office (foreign key to Member resource) |
OfficeManagerMlsId |
string | The lead Office Manager for the given office |
OfficeEmail |
string | The email address of the office |
ModificationTimestamp |
timestamp | Date/time the roster (member or office) record was last modified |
Database Resource
Retrieve list of available MLS databases you have access to.
Endpoint: GET /v1/Database
Available Fields
| Field | Type | Description |
|---|---|---|
MLSID |
integer | Database ID (Primary Key, Auto-increment) |
MLSName |
string | Database name (Primary Key) |
MLSLegalName |
string | Full legal name of MLS |
UpdateDate |
datetime | Last update timestamp |
OriginatingSystemName |
string | The originating system identifier used in Property, Member, and Office API queries |
[i] Important: The Database resource does not require OriginatingSystemName in the $filter parameter. Instead, query this endpoint first to retrieve the list of available databases. Use the OriginatingSystemName field from the response in your Property, Member, and Office queries.
OData Parameters
The API supports OData query parameters for flexible data retrieval. All parameters are optional except for OriginatingSystemName in $filter.
$filter - Filtering Data
Filter results using comparison and logical operators.
Comparison Operators
| Operator | Description | Example |
|---|---|---|
eq |
Equal to | ListPrice eq 500000 |
ne |
Not equal to | ListAgentMlsId ne '12345' |
gt |
Greater than | ListPrice gt 300000 |
ge |
Greater than or equal | ListPrice ge 300000 |
lt |
Less than | ListPrice lt 1000000 |
le |
Less than or equal | ListPrice le 1000000 |
Logical Operators
| Operator | Description | Example |
|---|---|---|
and |
Logical AND | ListPrice gt 300000 and ListPrice lt 500000 |
or |
Logical OR | ListAgentMlsId eq '123' or ListAgentMlsId eq '456' |
not |
Logical NOT | not(ListPrice gt 1000000) |
( ) |
Grouping | (ListPrice gt 300000 and ListPrice lt 500000) or ClosePrice gt 600000 |
OriginatingSystemName Requirement
[!] Required: All requests to Property, Member, and Office resources must include OriginatingSystemName in the $filter parameter to specify which MLS database to query.
GET /v1/Property?$filter=OriginatingSystemName eq 'TRREB' and ListPrice gt 500000
Database Selection Workflow
Step 1: Query GET /v1/Database to retrieve available databases
Step 2: Extract the OriginatingSystemName value from the response
Step 3: Use this value in $filter parameter for Property, Member, and Office queries
Example: If Database response includes "OriginatingSystemName": "TRREB", use it as:
$filter=OriginatingSystemName eq 'TRREB' and ListPrice gt 500000
Filter Examples
$filter=OriginatingSystemName eq 'TRREB' and ListPrice gt 500000
$filter=OriginatingSystemName eq 'TRREB' and ListPrice gt 300000 and ListPrice lt 800000 and CloseDate ge '2024 -01-01'
$filter=OriginatingSystemName eq 'TRREB' and (ListPrice gt 500000 or ClosePrice gt 600000)
$select - Field Selection
Specify which fields to return in the response. Omit this parameter to return all fields.
$select=ListingId,ListPrice,UnparsedAddress,CloseDate
[i] Performance Tip: Use $select to reduce payload size and improve response times by only requesting fields you need.
$orderby - Sorting
Sort results by one or more fields in ascending (asc) or descending (desc) order.
$orderby=ListPrice desc
$orderby=CloseDate desc,ListPrice asc
$top - Result Limit
Limit the number of records returned. Default is 100 records, maximum is 5000 records per request.
$top=50
$skip - Pagination Offset
Skip a specified number of records (useful for pagination).
$top=100&$skip=100
Combining Parameters
You can combine multiple OData parameters in a single request:
GET /v1/Property?$filter=OriginatingSystemName eq 'TRREB' and ListPrice gt 400000 and ListPrice lt 600000&$select=ListingId,ListPrice,UnparsedAddress&$orderby=ListPrice desc&$top=50&$skip=0
Code Examples
Complete examples in multiple programming languages showing authentication and data retrieval.
cURL
# Step 1: Get access token
TOKEN_RESPONSE=$(curl -s -X POST https://ims-api.restats.com/authorization/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=api" \
-d "grant_type=client_credentials")
# Extract token
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
# Step 2: Query properties
curl -X GET "https://ims-api.restats.com/v1/Property?\$filter=OriginatingSystemName eq 'TRREB' and ListPrice gt 500000&\$top=10" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Python
import requests
# Configuration
BASE_URL = "https://ims-api.restats.com"
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
# Step 1: Get access token
token_response = requests.post(
f"{BASE_URL}/authorization/token",
data={
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "api",
"grant_type": "client_credentials"
}
)
if token_response.status_code == 200:
access_token = token_response.json()["access_token"]
# Step 2: Query properties
headers = {"Authorization": f"Bearer {access_token}"}
params = {
"$filter": "OriginatingSystemName eq 'TRREB' and ListPrice gt 500000",
"$select": "ListingId,ListPrice,UnparsedAddress,ListOfficeEmail,BuyerOfficeEmail",
"$top": 10
}
data_response = requests.get(
f"{BASE_URL}/v1/Property",
headers=headers,
params=params
)
if data_response.status_code == 200:
properties = data_response.json()["value"]
print(f"Found {len(properties)} properties")
for prop in properties:
print(f" {prop['ListingId']}: ${prop['ListPrice']:,.0f} - {prop['UnparsedAddress']}")
else:
print(f"Error: {data_response.status_code} - {data_response.text}")
else:
print(f"Authentication failed: {token_response.status_code}")
JavaScript (Node.js)
const fetch = require('node-fetch');
// Configuration
const BASE_URL = 'https://ims-api.restats.com';
const CLIENT_ID = 'YOUR_CLIENT_ID';
const CLIENT_SECRET = 'YOUR_CLIENT_SECRET';
async function getIMSData() {
try {
// Step 1: Get access token
const tokenResponse = await fetch(`${BASE_URL}/authorization/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: 'api',
grant_type: 'client_credentials'
})
});
if (!tokenResponse.ok) {
throw new Error(`Authentication failed: ${tokenResponse.status}`);
}
const { access_token } = await tokenResponse.json();
// Step 2: Query properties
const params = new URLSearchParams({
'$filter': "OriginatingSystemName eq 'TRREB' and ListPrice gt 500000",
'$select': 'ListingId,ListPrice,UnparsedAddress,ListOfficeEmail',
'$top': '10'
});
const dataResponse = await fetch(
`${BASE_URL}/v1/Property?${params}`,
{
headers: {
'Authorization': `Bearer ${access_token}`
}
}
);
if (!dataResponse.ok) {
throw new Error(`Data request failed: ${dataResponse.status}`);
}
const data = await dataResponse.json();
console.log(`Found ${data.value.length} properties`);
data.value.forEach(prop => {
console.log(` ${prop.ListingId}: $${prop.ListPrice.toLocaleString()} - ${prop.UnparsedAddress}`);
});
} catch (error) {
console.error('Error:', error.message);
}
}
getIMSData();
PHP
$clientId,
'client_secret' => $clientSecret,
'scope' => 'api',
'grant_type' => 'client_credentials'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$tokenResponse = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
$tokenData = json_decode($tokenResponse, true);
$accessToken = $tokenData['access_token'];
// Step 2: Query properties
$filterQuery = http_build_query([
'$filter' => "OriginatingSystemName eq 'TRREB' and ListPrice gt 500000",
'$select' => 'ListingId,ListPrice,UnparsedAddress',
'$top' => '10'
]);
$ch = curl_init($baseUrl . '/v1/Property?' . $filterQuery);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$dataResponse = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
$data = json_decode($dataResponse, true);
echo "Found " . count($data['value']) . " properties\n";
foreach ($data['value'] as $prop) {
echo " {$prop['ListingId']}: \${$prop['ListPrice']} - {$prop['UnparsedAddress']}\n";
}
} else {
echo "Data request failed: HTTP $httpCode\n";
}
} else {
echo "Authentication failed: HTTP $httpCode\n";
}
?>
Response Format
All successful responses return JSON with a consistent structure.
Success Response
Status Code: 200 OK
{
"value": [
{
"ListingId": "C123456",
"UnparsedAddress": "123 Main Street, Toronto, ON",
"ListPrice": 599000.00,
"ClosePrice": 595000.00,
"CloseDate": "2024-01-15",
"ListAgentMlsId": "AG12345",
"BuyerAgentMlsId": "AG67890",
"ListOfficeMlsId": "OF111",
"BuyerOfficeMlsId": "OF222",
"ListOfficeEmail": "listings@realestate-office.com",
"BuyerOfficeEmail": "buyers@realty-group.com",
"ModificationTimestamp": "2024-01-20T15:30:00"
},
{
"ListingId": "C123457",
"UnparsedAddress": "456 Oak Avenue, Toronto, ON",
"ListPrice": 725000.00,
"ClosePrice": 720000.00,
"CloseDate": "2024-01-18",
"ListAgentMlsId": "AG23456",
"BuyerAgentMlsId": "AG78901",
"ListOfficeMlsId": "OF333",
"BuyerOfficeMlsId": "OF444",
"ListOfficeEmail": "list@torontorealty.com",
"BuyerOfficeEmail": "contact@buyersagency.com",
"ModificationTimestamp": "2024-01-21T10:15:00"
}
]
}
Response Structure
| Field | Type | Description |
|---|---|---|
value |
array | Array of result objects |
[i] Note: The structure of each object in the value array depends on the resource and fields selected. See the Resources section for field details.
Error Handling
When an error occurs, the API returns an appropriate HTTP status code and a JSON error response.
Error Response Format
{
"error": {
"code": 401,
"message": "OriginatingSystemName must be sent"
}
}
HTTP Status Codes
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid request parameters or syntax |
| 401 | Unauthorized | Missing OriginatingSystemName, invalid credentials, or validation error |
| 403 | Forbidden | Missing or invalid authentication token |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error occurred |
Common Error Examples
Missing Authentication Token
Status Code: 403 Forbidden
{
"error": "Bearer Token is missing"
}
Invalid Credentials
Status Code: 400 Bad Request
{
"error": "Invalid credentials"
}
Missing OriginatingSystemName
Status Code: 401 Unauthorized
{
"error": {
"code": 401,
"message": "OriginatingSystemName must be sent"
}
}
Invalid Field Name
Status Code: 401 Unauthorized
{
"error": {
"code": 401,
"message": "Column InvalidFieldName is not allowed."
}
}
$top Value Exceeded
Status Code: 401 Unauthorized
{
"error": "$top value can not exceed 5000"
}
Resource Not Found
Status Code: 404 Not Found
{
"error": "Resource can not be found"
}
Rate Limiting
The API tracks all requests for rate limiting and monitoring purposes.
Rate Limit Response
If you exceed the rate limit, you'll receive a 429 status code:
{
"error": "Rate limit exceeded: 1050/1000 requests per hour",
"retry_after": 3600
}
Best Practices
- Implement exponential backoff for retry logic
- Cache responses when appropriate
- Use $select to minimize payload size
- Batch requests efficiently using $top and $skip
- Monitor your usage patterns
Best Practices
Performance Optimization
1. Use $select to Limit Fields
Only request the fields you need to reduce payload size and improve response times.
$select=ListingId,ListPrice,UnparsedAddress
2. Implement Pagination
Use $top and $skip for efficient pagination instead of requesting all records.
page_size = 100
page = 1
skip = (page - 1) * page_size
params = {
'$filter': "OriginatingSystemName eq 'TRREB'",
'$top': page_size,
'$skip': skip
}
3. Filter Server-Side
Use $filter to reduce data transferred instead of filtering client-side.
4. Reuse Access Tokens
Tokens are valid for 150 minutes. Store and reuse them instead of requesting new tokens for each request.
Error Handling
Implement Retry Logic
import time
def make_request_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
elif response.status_code == 429: # Rate limit
retry_after = int(response.headers.get('Retry-After', 60))
time.sleep(retry_after)
else:
print(f"Error: {response.status_code}")
break
return None
Security
- Never expose your client_secret in client-side code
- Store credentials securely using environment variables
- Use HTTPS for all requests (enforced by API)
- Implement proper token refresh logic
- Monitor API usage for unusual patterns
Data Freshness
Use the ModificationTimestamp field to track when records were last updated and implement incremental syncing:
$filter=OriginatingSystemName eq 'TRREB' and ModificationTimestamp ge '2024-01-01T00:00:00'
$orderby=ModificationTimestamp asc