IMS API Documentation

RESTful API for accessing property, member, and office data with OData query support

https://ims-api.restats.com

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

  1. Obtain your API credentials (client_id and client_secret)
  2. Generate an access token using the /authorization/token endpoint
  3. Make requests to /v1/{resource} endpoints with your token
  4. 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
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)

JSON
{
  "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:

HTTP Header
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

HTTP Request
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.

Example
GET /v1/Property?$filter=OriginatingSystemName eq 'TORONTO ON' and ListPrice gt 500000

Filter Examples

Simple Filter
$filter=OriginatingSystemName eq 'TORONTO ON' and ListPrice gt 500000
Multiple Conditions
$filter=OriginatingSystemName eq 'TORONTO ON' and ListPrice gt 300000 and ListPrice lt 800000 and CloseDate ge '2024 -01-01'
With Grouping
$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.

Example
$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.

Single Field
$orderby=ListPrice desc
Multiple Fields
$orderby=CloseDate desc,ListPrice asc

$top - Result Limit

Limit the number of records returned. Default is 100 records, maximum is 5000 records per request.

Example
$top=50

$skip - Pagination Offset

Skip a specified number of records (useful for pagination).

Example - Page 2
$top=100&$skip=100

Combining Parameters

You can combine multiple OData parameters in a single request:

Complete Example
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

Bash
# 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

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)

JavaScript
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

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

JSON Response
{
  "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

JSON Error
{
  "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

Response
{
  "error": "Bearer Token is missing"
}

Invalid Credentials

Status Code: 400 Bad Request

Response
{
  "error": "Invalid credentials"
}

Missing OriginatingSystemName

Status Code: 401 Unauthorized

Response
{
  "error": {
    "code": 401,
    "message": "OriginatingSystemName must be sent"
  }
}

Invalid Field Name

Status Code: 401 Unauthorized

Response
{
  "error": {
    "code": 401,
    "message": "Column InvalidFieldName is not allowed."
  }
}

$top Value Exceeded

Status Code: 401 Unauthorized

Response
{
  "error": "$top value can not exceed 5000"
}

Resource Not Found

Status Code: 404 Not Found

Response
{
  "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:

429 Response
{
  "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.

Good
$select=ListingId,ListPrice,UnparsedAddress

2. Implement Pagination

Use $top and $skip for efficient pagination instead of requesting all records.

Example
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

Python Example
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:

Incremental Sync
$filter=OriginatingSystemName eq 'TORONTO ON' and ModificationTimestamp ge '2024-01-01T00:00:00'
$orderby=ModificationTimestamp asc