Getting started

Getting started

Introduction

Centris provides a Data Distribution REST API certified by the Real Estate Standards Organization (RESO). This API lets third-party systems get access to MLS data such as listings, real estate agents, etc.

This document describes how to access and use the API.


Configuration

Test environment

Key Value
Data Distribution Root URL https://stg-datadistributionqc.centristst.ca/
Identity Provider Root URL https://stg-accounts.centristst.ca/

Production environment

Key Value
Data Distribution Root URL https://datadistributionqc.centris.ca/
Identity Provider Root URL https://accounts.centris.ca/

Client credentials

Centris will provide keys that will let you access the API.

  • Client ID: This is equivalent to your username.
  • Client secret: This is equivalent to your password.

Accessing the API

Authentication

The API is protected using an OpenID Connect (OIDC) layer from its Identity Provider.

When a system is authenticated through the OIDC flow, an access token is returned which represents the identity of that system. This token must be provided in each request to the API. If the token is not provided, expired or invalid, the API will return a 401 - Unauthorized response.

To get an access token, you will need to use the Client Credentials flow.

1
2
3
4
POST https://accounts.centris.ca/connect/token
grant_type=client_credentials
&client_id=your_client_id
&client_secret=your_client_secret

If the credentials are valid, the Identity Provider will return a 200 - OK with an access token.

The access token is a standard JSON Web Token (JWT) that looks like eyJhbGciOiJiIsIm…

Because the access token has a specific lifetime and will expire, you must make sure to request a new one at the appropriate time. The expires_in property lets you know when (in seconds) this token will expire.

To provide the access token, simply specify it in the Authorization header of the API request.

1
2
GET https://datadistributionqc.centris.ca/v1/odata/$metadata
Authorization: Bearer your_access_token

Assuming the request is authorized (see below), you should receive a 200 - OK response if the operation completed successfully.

Authorization

Once a request is authenticated, it must go through the next validation process which is the authorization. This process validates that the authenticated user is authorized to access the requested resource. If the request is unauthorized, the API will return a 403 - Forbidden response.

Rate limits

The number of requests to the API is limited to ensure its stability. If the system detects excessive usage, a 429 – Too Many Requests response will be returned. This doesn’t mean that your request is invalid or unauthorized; it simply means that you should retry the same request later. To help you identify when you should retry, the response will have important information.

The body of the response will look like the following.

1
2
3
4
{
    "message": "Quota excedeed",
    "details": "Quota exceeded. Maximum allowed: 20 per 1m. Please try again in 41 second(s)."
}

The headers of the response will contain the following.

1
Retry-After: 41

You can also try to avoid 429 – Too Many Requests responses before the limit is reached by looking at the headers of 200 – OK responses which will contain the following.

1
2
3
X-Rate-Limit-Limit: 1d
X-Rate-Limit-Remaining: 480
X-Rate-Limit-Reset: 2024-03-26T16:42:30.7526366Z

Status codes

Status code Description
200 - OK Everything is working as expected.
400 – Bad Request The request is considered invalid; update your request.
401 – Unauthorized The request could not be authenticated; provide a valid access token.
403 – Forbidden The request could be authenticated but access to the resource is forbidden.
404 – Not Found The requested content doesn’t exist or is no longer available.
429 – Too Many Requests The maximum number of requests was exceeded; wait before retrying the request.
500 – Internal Server Error An unexpected error has occurred; this may be temporary or not.

Querying the API

A word about RESO

RESO (Real Estate Standards Organization) is an independent and nonprofit organization which has the mission to:

“create and promote the adoption and utilization of standards that drive efficiency throughout the real estate industry.”

With the collaboration of multiple different organizations, RESO defines standards that help reduce the frictions between systems.

This is done via two main standards.

  • RESO Data Dictionary: Contains the terminology to be used for real estate data. For example, the list price of a listing should be named ListPrice. For more information, see RESO’s official article.

  • RESO Web API: Contains the communication protocol to be used to exchange real estate data. For example, it should be a REST API which follows the OData standard. For more information, see RESO’s official article.

Each year, RESO updates their standards to keep up with the evolving market.

With its certification, Centris aligns with RESO’s standards while also including non-standard resources and fields (known as local resources) which are not yet supported by RESO. These local resources may be integrated in RESO’s standards in the future.

For more information regarding the certification process, see RESO’s official website. You can also access RESO’s Data Dictionary from their wiki.

Functionalities of OData

Summary

The API follows the conventions defined in the standard OData specification. OData (Open Data Protocol) defines ways to interact with a REST API and was selected by RESO as the query language to reduce frictions between different systems.

Because OData is a standard, there are libraries in different programming languages that are built to ease the integration with an OData API. We suggest that you take some time to search if there is one that fits your needs. In any case, it is still very easy to integrate with the API.

An OData query may look like the following.

1
/Property?$select=ListingKey,ListPrice&$filter=StandardStatus eq 'Active'&$orderby=ModificationTimestamp&$count=true

This query returns the listing key and list price of active listings ordered by modification timestamp and includes the total count.

1
2
3
4
5
6
7
8
9
{
  "@odata.count": 133,
  "value": 
  [
    { "ListPrice": 123412.00, "ListingKey": "14689588" },
    { "ListPrice": 567632.00, "ListingKey": "14239483" },
    { "ListPrice": 890221.00, "ListingKey": "23659600" }
  ]
}

Operators

OData offers a broad range of operators but only a few of them are generally used. The following operators are supported.

Operator Description
$select Defines the fields to project.
$filter Defines the filter to apply.
$orderby Defines the order to apply.
$count Defines whether to include the total count.
$expand Defines relational navigations to include.
$skip Defines the number of records to skip.
$top Defines the number of records to return.

See OData’s official website for a comprehensive list of the conventions.

Getting the data model

Route

1
GET /v1/odata/$metadata

Description

Gets the data model of the system.

  • An optional parameter $format can be provided to get the response in JSON instead of XML. $format=application/json

Example

1
GET https://datadistributionqc.centris.ca/v1/odata/$metadata

From this endpoint you can retrieve important information on the data model such as.

  • The resources available (e.g. Member, Office, Property, etc.)
  • The fields available (e.g. Member.MemberKey, Member.MemberFirstName, Property.ListPrice, etc.)
  • The data type of the fields (e.g. String, Decimal) along with their size information (e.g. Max length, Precision, etc.)
  • The navigation fields (e.g. Member.MemberSocialMedia, etc.)

Getting a collection of resources

Route

1
GET /v1/odata/Property

This example uses the Property resource (a listing), but any other resource could be used instead (e.g. Member, Office, etc.)

Description

Gets a collection of property resources (listings).

  • An optional parameter $select can be provided to project the results. (e.g. $select=ListingKey,ModificationTimestamp)

  • An optional parameter $filter can be provided to filter the results. (e.g. $filter=StandardStatus eq 'Active')

  • An optional parameter $orderby can be provided to order the results. (e.g. $orderby=ModificationTimestamp)

  • An optional parameter $count can be provided to get the total count of the results. (e.g. $count=true)

  • An optional parameter $expand can be provided to include related data. (e.g. $expand=ListAgent)

Example

1
GET https://datadistributionqc.centris.ca/v1/odata/Property?$select=ListingKey,ModificationTimestamp&$filter=StandardStatus eq 'Active'&$orderby=ModificationTimestamp&$count=true&$expand=ListAgent(select=MemberFullName)

If no results match the request, a 200 – OK response with an empty collection will be returned.

Only some fields can be used with the $orderby and $filter operators. The API will return a 400 – Bad Request with details of the error in the response if an unauthorized field is used.

Getting a resource by its key

Route

1
GET /v1/odata/Property('12345')

This example uses the Property resource (a listing), but any other resource could be used instead (e.g. Member, Office, etc.)

Description

Gets a single property resource (listing).

  • An optional parameter $select can be provided to project the results. (e.g. $select=ListingKey,ModificationTimestamp)

  • An optional parameter $expand can be provided to include related data. (e.g. $expand=ListAgent)

Example

1
GET https://datadistributionqc.centris.ca/v1/odata/Property('12345')?$select=ListingKey,ModificationTimestamp&$expand=ListAgent(select=MemberFullName)

If no result match the request, a 404 – Not Found response will be returned.

Paginating through results

Server-side pagination

OData offers a simple way for systems to paginate through result sets using the nextLink field. If more results are available, a generated nextLink field will be included in the response; much like a cursor. To paginate through the complete result set, you can simply follow the next links (i.e. executing a new request using the returned url) until there is no nextLink.

A response may look like the following.

1
2
3
4
5
6
7
8
9
{
  "value": 
  [
    { "ListPrice": 123412.00, "ListingKey": "14689588" },
    { "ListPrice": 567632.00, "ListingKey": "14239483" },
    { "ListPrice": 890221.00, "ListingKey": "23659600" }
  ],
  "@odata.nextLink": "https://datadistributionqc.centris.ca/v1/odata/Property?$select=ListingKey%2CListPrice&$filter=StandardStatus%20eq%20%27Active%27&$orderby=ModificationTimestamp&$skip=10"
}

You can see that the operators of the original request were included in the nextLink with the addition of the $skip operator.

If you provide the $top operator, the nextLink will not be returned as the result set is considered complete.

Client-side pagination

You can also implement your own pagination using a combination of the $filter, $top and $orderby operators. You would need to provide the last value from the response to the $filter operator.

For example,

1
/Property?$filter=ModificationTimestamp gt 2024-02-05T17:03:58Z

By default, the collection is ordered by the following fields.

  • ModificationTimestamp - Ascending
  • Resource key (e.g. MemberKey, ListingKey, etc.) - Ascending

Working with multiple languages

Centris supports both French and English in its MLS data. The API can distribute the data in the different languages while still being non-breaking with RESO’s standards.

For example, even if Member.MemberFirstName would be Member.Prénom in French, the field MemberFirstName is still used; we only translate the payload of the response.

By default, the payload returned by the API is provided in English.

There are two ways to get the content in French.

Using the Accept-Language header

An optional header Accept-Language can be provided to receive the payload in a different language (e.g. Accept-Language: fr). You will need to do multiple requests to get the content in all languages which may not be practical depending on your use case.

Using the Translations expansion

An optional query parameter $expand=Translations can be provided to include all translations of the resource (e.g. /Member?$expand=Translations). You will get all translations in a single request but will need to do some interpretation of the results.

The response may look like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
    "MemberKey": "1234",
    "MemberStatus": "Active",
    "MemberIntroduction": "My introduction in English",
    "MemberCorporationType": "My corporation type",
    "Translations": 
    [
        {
            "Locale": "fr",
            "Values": 
            [ 
                {
                    "FieldName": "MemberIntroduction",
                    "Value": "Mon introduction en Français"
                },
                {
                    "FieldName": "MemberCorporationType",
                    "Value": "Mon type de corporation"
                }
            ]
        },
        {
            "Locale": "en",
            "Values": 
            [ 
                {
                    "FieldName": "MemberIntroduction",
                    "Value": "My introduction in English"
                },
                {
                    "FieldName": "MemberCorporationType",
                    "Value": "My corporation type"
                }
            ]
        }

    ]
}

You can see that there are two locales being returned (fr and en) which both contains two translated fields (MemberIntroduction and MemberCorporationType).

If you expand on a resource that itself has translations, you will need to expand the translations on that resource as well; multiple expansions can be nested in a single request (e.g. Property?$expand=Translations,Expenses(expand=Translations)).

Replication

Summary

While the API allows live querying, its main objective is to allow other systems to replicate MLS data so that they can use it on their own. There are a few concepts to consider regarding replication.

Initializing your system

This is when your system doesn’t have any MLS data yet. You will request the different resources from an empty starting point and paginate through the result sets. For example, for the Property resource, you would start with /Property and follow the nextLink.

Keeping your system up to date

This is when your system has been initialized and needs to stay updated.

  • Getting the latest changes by continuously pulling: Your system keeps track of the last ModificationTimestamp of the different resources and requests records updated after this timestamp. For the Property resource, your request may look like this.
1
/Property?$filter=ModificationTimestamp gt 2024-02-05T17:03:58Z
  • Receiving the latest changes using webhooks: Instead of continuously pulling the data, the API can let you know when a record has been updated. Note that this is optional. Your system could receive something like this.
1
2
3
4
5
{
  "ResourceName": "Property",
  "ResourceRecordKey": "18634444",
  "ResourceRecordUrl": "https://datadistributionqc.centris.ca/v1/odata/Property('18634444')"
}
  • Reconciling your system: This is the process of comparing the complete list of resource keys and modification timestamps between your system and the API. This is a safety net in case your system may have missed an update. For the Property resource, your request may look like this.
1
/Property?$select=ListingKey,ModificationTimestamp