Collapse Drawer
Expand Drawer

OData Reference

info icon

Check out our Postman Collection to see OData in action

$filter

GET /trestle/odata/Property?$filter=ListingKey%20eq%20%27123456%27 HTTP/1.1
Host: api-prod.corelogic.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni...3wQJVBA
String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .filter("ListingKey eq '123456'")
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);
String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .filter(oDataClient.getFilterFactory().and(
                oDataClient.getFilterFactory().ge("BedroomsTotal", 4),
                oDataClient.getFilterFactory().ge("BathroomsTotalInteger", 2)
        ))
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);
var serviceUri = "https://api-prod.corelogic.com/trestle/odata/";
var client = new RestClient(serviceUri);
var request = new RestRequest("Property");
request.AddParameter("$filter", "ListingKey eq '123456'", ParameterType.QueryString);
var response = client.Execute(request);

The Basics

Operator Meaning Example
eq Equal To ListingKey eq '123456'
ne Not Equal To AssociationYN ne True
gt Greater Than BedroomsTotal gt 3
ge Greater Than or Equal To BathroomsTotalInteger ge 2
lt Less Than ListPrice lt 350000
le Less Than or Equal To LotSizeAcres le 5
and Both conditions are true BedroomsTotal eq 3 and BathroomsTotalInteger eq 2
or Either condition is true BedroomsTotal ge 3 or BathroomsTotalInteger ge 3
not Condition is not true not (BedroomsTotal eq 3)
has Multi-Select Has Value WaterfrontFeatures has CanalAccess
in Value is in List StandardStatus in ('Active', 'Pending')

These operators can be combined to form very complex queries, e.g. ((ListPrice lt 300000 and LotSizeAcres lt 3) or (BedroomsTotal gt 4 and StandardStatus eq 'Active'))

Working with numbers

There are three basic types of numbers represented in OData: integer, decimal, and floating point. The integers are all signed and range from 8-bit (Edm.SByte) to 64-bit (Edm.Int64). The floating point numbers can be 32-bit single precision (Edm.Single) or 64-bit double precision (Edm.Double).

When working with mixed numeric types (e.g. Edm.Int32 and Edm.Double) values get "promoted" to larger types according to the following precedence table:

Type Description
Edm.Double 64-bit double precision floating point
Edm.Single 32-bit single precision floating point
Edm.Decimal Numeric value with a decimal
Edm.Int64 64-bit signed integer
Edm.Int32 32-bit signed integer
Edm.Int16 16-bit signed integer
Edm.SByte 8-bit signed integer

The standard arithmetic operators are available in OData:

Operation OData representation
Addition Value1 add Value2
Subtraction Value1 sub Value2
Multiplication Value1 mul Value2
Division Value1 div Value2
Modulo Value1 mod Value2
Negation -Value1

Working with strings

Strings are relatively straight forward in Trestle. They are always enclosed in single quotes (') and if you need a single quote in the string, you simply double it up, e.g. 'O''Brien'.

In addition to all of the basic operators that you can use with strings, there are functions to help out as well:

Function Description
contains(haystack, needle) Returns true if needle is found in haystack
endswith(haystack, needle) Returns true if haystack ends with needle
startswith(haystack, needle) Returns true if haystack starts with needle
length(value) Returns the length of the string value
indexof(haystack, needle) Returns the 0-based position of needle in haystack
tolower(value) Returns the string with all characters lower-cased
toupper(value) Returns the string with all characters upper-cased
trim(value) Returns the string with all leading and trailing whitespace removed

Working with dates and times

The dates and timestamps exposed through Trestle are exposed in UTC. You can use the following formats to refer to the dates or times:

Description Example
Date in YYYY-MM-DD format 2020-04-01
UTC timestamp in 24 hour format 2020-04-01T12:30:30Z
Non-UTC timestamp in 24 hour format 2020-04-01T12:30:30-05:00
Non-UTC timestamp with miliseconds in 24 hour format 2020-04-01T12:30:30.00-05:00
info icon

OpenHouse dates and times are exposed in the timezone of the MLS

info icon

Note, for positive offsets, make sure to encode the + sign as %2B e.g. 2020-04-01T12:30:30%2B05:00

Working with enumerations

While the OData spec indicates that enumerations should be referenced with their schema, e.g. CoreLogic.DataStandard.RESO.DD.Enums.StandardStatus'Closed', Trestle allows you to shortcut that by simply writing 'Closed'. For multi-select enumerations, you can use the has operator, e.g.:

  • Check for one value: Appliances has 'Dishwasher'
  • Check for two values: Appliances has 'Dishwasher,RangeHood'

$select

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .select("ListingKey,StandardStatus")
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);

You can limit the fields you want with $select. For example if you want just the ListingKey and the StandardStatus, you would use $select=ListingKey,StandardStatus. One caveat is that if you only select an expanded value (e.g. $select=Rooms), you will get all of the fields back in addition to the expanded value.

$count

GET /trestle/odata/Property?$count=true HTTP/1.1
Host: api-prod.corelogic.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni...3wQJVBA
var client = new RestClient("https://api-prod.corelogic.com/trestle/odata/Property?$count=true");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni...3wQJVBA");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .count(true)
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);

The resulting JSON will include @odata.count

{
    "@odata.context": "https://api-prod.corelogic.com/trestle/odata/$metadata#Property/CoreLogic.DataStandard.RESO.DD.Property",
    "@odata.nextLink": "https://api-prod.corelogic.com/trestle/odata/Property?$count=true&$skip=10",
    "@odata.count": 980150,
    "value": [
    ]
}

Using $count=true will return the total number of results that match your query. Because of the limits on how many records you get per query, the total count will often be more than the number of records returned by the query. To accoutn for this, you can use $top and $skip or the replication endpoint to iterate through the records until you download the total number of records.

$top

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .top(1000)
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);

Use $top to specify the number of records you want returned. For most queries, the maximum you can return is 1,000. For queries that only select key fields, you can return up to 300,000. If you need to retrieve more than 1,000,000 records, you will need to use the replication endpoint.

$skip

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .skip(1000)
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);

Use $skip to skip over a number of records. This is most useful in combination with $top to iterate over the results of a query that return more than 1,000 records.

$expand

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .expand("CustomProperty")
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);

Expand can take options as well

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
Map<QueryOption, Object> expandOptions = new HashMap<QueryOption, Object>();
expandOptions.put(QueryOption.SELECT, "MediaURL");
expandOptions.put(QueryOption.TOP, 1);
expandOptions.put(QueryOption.ORDERBY, "Order");
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .expandWithOptions("Media", expandOptions)
        .build();

You can use $expand to pull in data from related resources. In its simplest form, it looks like $expand=Rooms, which would pull in all of the associated rooms data. You can pull in data for more than one resource by listing them out separated by commas, e.g. $expand=Rooms,Units. You can also specify OData query parameters, e.g. $expand=Media($select=MediaURL;$top=1;$orderby=Order) to get just the MediaURL associated with the first photo for the listing.

$apply

Trestle has begun to implement the OData Extension for Data Aggregation. As of now, Trestle provides support for the following pieces of the extension:

$apply=groupby() (partial)

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .applyCustomQuery("$apply", "groupby((PostalCity))")
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);

You can use $apply=groupby() to get a list of unique values for a field or set of fields. For example, if you wanted to get a list of PostalCity values available to you, you would use $apply=groupby((PostalCity)). Note that there are two sets of parentheses. You are passing a set of field names as the first parameter to the function (note: Trestle does not currently support the second parameter).

info icon

The groupby function will only return the first 10,000 unique values.

Trestle OData Extensions

Replication=True

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .addCustomQueryOption("Replication", "true")
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);
{
    "@odata.context": "https://api-prod.corelogic.com/trestle/odata/$metadata#Property/CoreLogic.DataStandard.RESO.DD.Property",
    "@odata.nextLink": "https://api-prod.corelogic.com/trestle/odata/Property/replication?id=123-a119257f-5dc0-4a47-bbb3-e526efd22656",
    "value": [ ... ]
}

If you are replicating data from Trestle, then you are likely already using the standard pattern of specifying a $top and a $skip to page over the results. If, however, you are attempting to replicate a resource that has more than one million (1,000,000) records in it, then you will need to use Trestle's replication endpoint.

Using the replication endpoint is a two-step process. The first step is to indicate to Trestle that you want to use the replication endpoint. You do this by adding replication=true as a query parameter, e.g. /trestle/odata/Property?$select=ListingKey,StandardStatus,ListPrice&replication=true.

The second step is to repeatedly call the returned @odata.nextLink until you have fully replicated the data set. After the first request where you pass replication=true, you will get links that look like /trestle/odata/Property/replication?id=123-a119257f-5dc0-4a47-bbb3-e526efd22656.

info icon

After the first request, subsequent replication requests will be of the form /trestle/odata/<resource>/replication?id=<guid>

Beyond the maximum number of records retrievable, there are two main differences between replicating with the replication end point and with $top/$skip. The first is that you cannot skip records, so if your replication process fails part way through, you will have to restart it. The second is that you will need to use the returned @odata.nextLink instead of computing the next URL yourself.

As you can see in the provided JSON example, the @odata.nextLink looks different from the normal @odata.nextLinks. It now contains a new replication link containing a unique id for the replication request.

info icon

Returned replication links expire after 5 minutes of inactivity

PrettyEnums=True

String service_uri = "https://api-prod.corelogic.com/trestle/odata/";
ODataClient oDataClient = ODataClientFactory.getClient();
URI uri = oDataClient.newURIBuilder(service_uri)
        .appendEntitySetSegment("Property")
        .addCustomQueryOption("PrettyEnums", "true")
        .build();
ODataEntitySetIteratorRequest<ClientEntitySet, ClientEntity> request =
        oDataClient.getRetrieveRequestFactory().getEntitySetIteratorRequest(uri);
<Member Name="ActiveUnderContract" Value="1">
  <Annotation Term="RESO.OData.Metadata.StandardName" String="Active Under Contract" />
</Member>

The OData specification defines enumeration values to consist only of letters, numbers, and the underscore (_) character. Trestle complies with these specifications, so the data you get from Trestle will look like ActiveUnderContract instead of Active Under Contract.

The metadata does provide annotations for the enumerations as seen in the XML sample. While using the annotations in the metadata is The Right Way to get the human-friendly versions of the enumerated values, Trestle provides a simpler way: passing PrettyEnums=true in your query.

With PrettyEnums=true, Trestle will do the translations for you, so that instead of ActiveUnderContract, you will get Active Under Contract. In this way, Trestle simplifies the process of pulling data and displaying it to your end users.