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 |
|---|---|---|
ListingId |
string | Unique listing identifier (Primary Key) |
UnparsedAddress |
string | Full property address |
ListPrice |
number | Property listing price |
ClosePrice |
number | Final sale price |
CloseDate |
date | Sale closing date |
ListAgentMlsId |
string | Listing agent MLS ID |
BuyerAgentMlsId |
string | Buyer agent MLS ID |
ListOfficeMlsId |
string | Listing office MLS ID |
BuyerOfficeMlsId |
string | Buyer office MLS ID |
ModificationTimestamp |
timestamp | Last modification date/time |
Member Resource
Access agent and member information including contact details and office affiliations.
Endpoint: GET /v1/Member
Available Fields
| Field | Type | Description |
|---|---|---|
MemberFullName |
string | Member full name (Primary Key) |
MemberEmail |
string | Member email address |
MemberMlsId |
string | Member MLS ID (Primary Key) |
OfficeMlsId |
string | Associated office MLS ID |
OfficeName |
string | Associated office name |
MemberMobilePhone |
string | Member mobile phone number |
ModificationTimestamp |
timestamp | Last modification date/time |
Office Resource
Access real estate office information including addresses and contact details.
Endpoint: GET /v1/Office
Available Fields
| Field | Type | Description |
|---|---|---|
OfficeMlsId |
string | Office MLS identifier |
OfficeName |
string | Office name (Primary Key) |
OfficeAddress |
string | Office street address (Primary Key) |
OfficePostalCode |
string | Office postal/zip code |
OfficeCity |
string | Office city |
OfficePhone |
string | Office phone number |
ModificationTimestamp |
timestamp | Last modification date/time |
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 |
[i] Note: The Database resource does not require OriginatingSystemName in the $filter parameter.
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 'TORONTO ON' and ListPrice gt 500000
Filter Examples
$filter=OriginatingSystemName eq 'TORONTO ON' and ListPrice gt 500000
$filter=OriginatingSystemName eq 'TORONTO ON' and ListPrice gt 300000 and ListPrice lt 800000 and CloseDate ge '2024 -01-01'
$filter=OriginatingSystemName eq 'TORONTO ON' 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 'TORONTO ON' 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 'TORONTO ON' 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 'TORONTO ON' and ListPrice gt 500000",
"$select": "ListingId,ListPrice,UnparsedAddress",
"$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 'TORONTO ON' and ListPrice gt 500000",
'$select': 'ListingId,ListPrice,UnparsedAddress',
'$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 'TORONTO ON' 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",
"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",
"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 'TORONTO ON'",
'$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 'TORONTO ON' and ModificationTimestamp ge '2024-01-01T00:00:00'
$orderby=ModificationTimestamp asc