API Documentation

The Insights API provides access to Property data for US properties. The Insights API follows OData protocol with the addition of custom querying options.

Authentication

Access to the API utilizes OAuth2.

Client Credentials (ID and secret) are provided for each user of the API, allowing for retrieval of bearer access token with preset expiry (default 3600 seconds).

We must encode the combination of Client ID and Client Secret Key separated by ":" in Base 64 format like this:

Base64Encode(5pq9999coididi613e222o1nnpp: fhkoq33a69d8191j1tercv35037clb9e5a7d215e64e4e) = 85e1b0e7539a4a96b00ee02b335aa6418c580917a6b4667f6f7a6fe2149536041569924dfbe4a7df33f==

Can use online service Base64Encode for the encoding.

The end point for the token must include two parameters with optional scope parameter:

  1. "grant_type" = "client_credentials"
  2. "client_id" = "5pq9999coididi613e222o1nnpp"
  3. To authenticate successfully, if providing scope parameter, ensure the scope is set to either:
    "scope" = "" (empty string) or "scope" = "https://api.constellation1apis.com/Api.Read" depending on your integration requirements.
Important: The "Authorization: Bearer <token>" header is required on every request. The API is stateless and does not maintain sessions. Requests missing this header will be rejected with a 401 Unauthorized response regardless of prior successful calls.

Example request to retrieve access token:

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic 85e1b0e7539a4a96b00ee02b335aa6418c580917a6b4667f6f7a6fe2149536041569924dfbe4a7df33f==" \
-H "Host: authenticate.constellation1apis.com" \
"https://authenticate.constellation1apis.com/oauth2/token?grant_type=client_credentials&client_id=5pq9999coididi613e222o1nnpp"

Authentication response:

{
"access_token": "**********************************",
"expires_in": 3600,
"token_type": "Bearer"
}

Usage

Once the access token is retrieved, it is needed in each call to the CDatalabs API under the Authorization HTTP header:

curl -X GET -i -H "Host: insights.cdatalabs.com" \
-H "Authorization: Bearer **********************************" \
"https://insights.cdatalabs.com/odata/Assessor?$top=5"

Recommendation: It is recommended to include 'Accept-Encoding' header with value 'gzip, deflate, br'. This will compress the response into a zip/brotli format. This should improve the latency in transit by compressing the size. Client will have to decompress the response before processing it:

Header Value
Accept-Encoding gzip, deflate, br

Metadata

The Insights API has an endpoint to retrieve metadata:

https://insights.cdatalabs.com/odata/$metadata

This endpoint allows caller to know available fields and data types for each entity.

<?xml version="1.0" encoding="utf-8" ?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
    <edmx:DataServices>
        <Schema Namespace="Property.Api.Snowflake.Model" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityType Name="Assessor" />
            <EntityType Name="Assignment" />
            <EntityType Name="Avm" />
            
            <EntityType Name="Deed" />
            <EntityType Name="Mortgage" />
            <EntityType Name="PreForeclosure" />
            <EntityType Name="Release" />
            <EntityType Name="School" />
            <EntityType Name="SchoolReviews" />
            <EntityType Name="SchoolRanksAndDemographics" />
            <EntityType Name="SchoolTestScores" />
            <EntityType Name="Neighborhood Community" />
            <EntityType Name="Household Demographic" />
            <EntityType Name="Individual Demographic" />
            <EntityType Name="MortgagePropertyAggregate" />
            <EntityType Name="AssessorDeleteHistory" />
            <EntityType Name="DeedDeleteHistory" />
            <EntityType Name="MortgageDeleteHistory" />
            <EntityType Name="AssignmentDeleteHistory" />
            <EntityType Name="ReleaseDeleteHistory" />
            <EntityType Name="PreForeclosureDeleteHistory" />
            <EntityType Name="BaseProperties" />
            <EntityType Name="BasePropertiesDeleteHistory" />
            <EntityType Name="ParcelBoundary" />
            <EntityType Name="CensusBoundary" />
            <EntityType Name="SchoolBoundary" />
            <EntityType Name="Schools" />
            <EntityType Name="SchoolDistricts" />
            <EntityType Name="SchoolRatings" />
            <EntityType Name="TestScores" />
            <EntityType Name="TestLookup" />
            <EntityType Name="MortgagePropertyAggregateDeleteHistory" />
            <EntityType Name="AvmHistoryAggregate" />
            <EntityType Name="SalesAndTransferHistory" />
            <EntityType Name="AssessorHistoryAggregate" />
            <EntityType Name="MortgageHistoryAggregate" />
            <EntityType Name="SalesAndTransferDeleteHistory" />
            <EntityType Name="LoanDeleteHistory" />
            
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>

Entities

The Insights API supports several entity types, primarily Assessor which represents a Property tax assessment. Other entity types such as Deed represents property ownership records.

Entity Description URL
Assessor Property Assessor Data https://insights.cdatalabs.com/odata/Assessor
Deed Property Deed https://insights.cdatalabs.com/odata/Deed
Mortgage Property Mortgage Data https://insights.cdatalabs.com/odata/Mortgage
Assignment Mortgage Assignment https://insights.cdatalabs.com/odata/Assignment
Release Mortgage Release https://insights.cdatalabs.com/odata/Release
AVM Consumer Grade AVM https://insights.cdatalabs.com/odata/AVM
PreForeclosure PreForeclosure https://insights.cdatalabs.com/odata/PreForeclosure
School School Information https://insights.cdatalabs.com/odata/School
SchoolReviews Parent & Pupil School Reviews https://insights.cdatalabs.com/odata/SchoolReviews
SchoolRanksAndDemographics School Ranking and Demographics History https://insights.cdatalabs.com/odata/SchoolRanksAndDemographics
SchoolTestScores School Test Scores History https://insights.cdatalabs.com/odata/SchoolTestScores
NeighborhoodCommunity Neighborhood Community https://insights.cdatalabs.com/odata/NeighborhoodCommunity
HouseholdDemographic Household Demographic https://insights.cdatalabs.com/odata/HouseholdDemographic
IndividualDemographic Individual Demographic https://insights.cdatalabs.com/odata/IndividualDemographic
MortgagePropertyAggregate Mortgage Property Aggregate https://insights.cdatalabs.com/odata/MortgagePropertyAggregate
AssessorDeleteHistory Assessor delete history records https://insights.cdatalabs.com/odata/AssessorDeleteHistory
DeedDeleteHistory Deed delete history records https://insights.cdatalabs.com/odata/DeedDeleteHistory
MortgageDeleteHistory Mortgage delete history records https://insights.cdatalabs.com/odata/MortgageDeleteHistory
AssignmentDeleteHistory Assignment delete history records https://insights.cdatalabs.com/odata/AssignmentDeleteHistory
ReleaseDeleteHistory Release delete history records https://insights.cdatalabs.com/odata/ReleaseDeleteHistory
PreForeclosureDeleteHistory Pre-foreclosure delete history records https://insights.cdatalabs.com/odata/PreForeclosureDeleteHistory
BaseProperties Base property / public-record aggregate https://insights.cdatalabs.com/odata/BaseProperties
BasePropertiesDeleteHistory Base properties delete history https://insights.cdatalabs.com/odata/BasePropertiesDeleteHistory
ParcelBoundary Parcel boundary geometry https://insights.cdatalabs.com/odata/ParcelBoundary
CensusBoundary Census boundary geometry https://insights.cdatalabs.com/odata/CensusBoundary
SchoolBoundary School boundary geometry https://insights.cdatalabs.com/odata/SchoolBoundary
Schools School records (curated) https://insights.cdatalabs.com/odata/Schools
SchoolDistricts School district records https://insights.cdatalabs.com/odata/SchoolDistricts
SchoolRatings School ratings and rankings https://insights.cdatalabs.com/odata/SchoolRatings
TestScores School / district test scores https://insights.cdatalabs.com/odata/TestScores
TestLookup Test score lookup reference https://insights.cdatalabs.com/odata/TestLookup
MortgagePropertyAggregateDeleteHistory Mortgage property aggregate delete history https://insights.cdatalabs.com/odata/MortgagePropertyAggregateDeleteHistory
AvmHistoryAggregate AVM history aggregate https://insights.cdatalabs.com/odata/AvmHistoryAggregate
SalesAndTransferHistory Sales and transfer history https://insights.cdatalabs.com/odata/SalesAndTransferHistory
AssessorHistoryAggregate Assessor history aggregate https://insights.cdatalabs.com/odata/AssessorHistoryAggregate
MortgageHistoryAggregate Mortgage history aggregate https://insights.cdatalabs.com/odata/MortgageHistoryAggregate
SalesAndTransferDeleteHistory Sales and transfer delete history https://insights.cdatalabs.com/odata/SalesAndTransferDeleteHistory
LoanDeleteHistory Loan delete history https://insights.cdatalabs.com/odata/LoanDeleteHistory

Flat Files

CDL distributes property, assessor, mortgage, deed, AVM, school, and related real estate data to clients and prospects via two channels:

  • Flat File Exports - CSV files exported to client S3 locations via Snowflake COPY INTO
  • Snowflake Data Shares - Live data shared directly to prospect/partner Snowflake accounts

Flat File Exports

CSV flat file exports are delivered on a configured schedule. The EXPORT_CONFIG table drives all exports - each row defines a dataset, frequency, and delivery method.

Export Frequencies

Frequency What It Means When It Runs
Dailies Incremental changes from last 24-36 hours Every day at 12:00 UTC
Monthly Full or incremental monthly refresh Runs daily at 04:00 UTC, picks up only on the configured day of month
Weekly Weekly full refresh Runs on the configured day
Full Complete dataset reload On-demand only (kept Disabled until needed for backfills)
Yearly Annual full refresh On-demand only

Folder Structure

Exports are partitioned into folders using different strategies depending on dataset size and delivery needs.

Folder Structure How Files Are Organized
None Single flat directory per dataset: {ExportName}/Frequency/Dataset/
Per State Partitioned by state: {ExportName}/Frequency/Dataset/YYYY/Mon/DD/StateCode/
Per Date Partitioned by date: {ExportName}/Frequency/Dataset/YYYY/Mon/DD/
Per Month Partitioned by month: {ExportName}/Frequency/Dataset/YYYY/Mon/

Per State/Date/Month structures use dedicated stored procedures in L40_EXPORT that iterate over each partition value and export files individually. The "None" structure uses direct COPY INTO statements driven by the config table.

Snowflake Data Shares

Data shares provide live, always-current data directly in a Snowflake account - no files, no ETL, no lag. CDL uses Snowflake Secure Data Sharing so recipients query shared objects in their own account without receiving a physical copy of the source data.

How Outbound Sharing Works

Outbound shares deliver CDL datasets to approved Snowflake accounts. Access is granted only to accounts you authorize; recipients see the shared views or tables you expose, not the underlying pipelines or vendor feeds that produce them.

  • Public Marketplace listings - Discoverable listings that any Snowflake account can request or subscribe to under the published terms.
  • Private / targeted listings - Listings limited to specific Snowflake accounts you invite.
  • Direct shares - Account-to-account shares created for a named recipient without a marketplace listing.

Typical setup:

  1. Define the share objects (views or tables) that contain only the datasets intended for delivery.
  2. Create a share or listing and grant it to the recipient Snowflake account(s).
  3. The recipient creates a database from the share and queries it with their own roles and warehouses.
  4. Updates in the shared objects appear automatically for the recipient - no re-export step.

How Inbound Sharing Works

Inbound shares are how CDL receives licensed data into Snowflake from upstream providers. Provider identity and feed details are contract-specific and are not listed in this documentation.

  • Providers grant a secure share (or listing) to CDL's Snowflake account.
  • CDL mounts the share as a read-only database and loads or transforms only what licensing allows into internal schemas.
  • Downstream products (APIs, flat files, outbound shares) are built from CDL-managed objects - not by exposing the original provider share to customers.

This pattern keeps vendor and partner identities, listing IDs, and source-system names out of customer-facing docs while still documenting the delivery model.

Scheduled Tasks

Three Snowflake Tasks orchestrate all exports and notifications.

Daily Export Task (Partitioned Exports)

  • Schedule: Every day at 12:00 UTC
  • What it does: Reads all Dailies exports with a folder structure (Per State, Per Date, Per Month) from EXPORT_CONFIG. For each enabled row whose execution date matches today, it calls the corresponding stored procedure which iterates over partitions and runs COPY INTO for each.
  • On success: Sends a summary email to skyline@cdatalabs.com
  • On failure: Sends error email to both the client and skyline@cdatalabs.com

Monthly Export Task (Partitioned Exports)

  • Schedule: Every day at 04:00 UTC
  • What it does: Same logic as the daily task, but for Monthly exports with a folder structure. Runs daily but only picks up configs whose execution date matches (e.g., day 15 of the month, or * for every day).
  • On success/failure: Same email behavior as daily task

Daily Export Email Notification

  • Schedule: Every day at 02:00 UTC
  • What it does: Sends client-facing daily export summary emails. For each client with alerts enabled in CLIENT_CONFIG, it reads today's TASK_LOGS entries, maps dataset names to friendly labels (e.g., TOMO_DEED becomes "Deed"), and compiles a summary showing each dataset, row count, and timestamp. Zero-row exports are suppressed.

Task Logs

Every export execution is logged in the TASK_LOGS table. This is the primary audit trail for all export activity.

Each log entry records:

  • DATASETNAME - which dataset was exported (e.g., BASEPROPERTIESAGGREGATE, MORTGAGEHISTORYAGGREGATE, L40_EXPORT.ASSESSORLEGACYMAIN)
  • CLIENTNAME - which client the export was for
  • LOG_MESSAGE - the outcome, typically "Total No of rows exported are {count}" for success, or "ERROR: {message}" for failures
  • LOG_TIME - when the export completed

Basic Requests

Following OData protocol, the following examples show how combining options and filters can retrieve a desired subset of data results.

GET /odata/Assessor?$select=...&$filter=...&$top=...&$orderby=...&$skip=...

API response structure


    {
    "@odata.context": "...",
    "@odata.total_count": 100,
    "@odata.count": 0,
    "@odata.nextLink": "...",
    "value": []
    }
Field Description
odata.context annotation of the request
odata.count number of records returned in response, default is 100
odata.totalCount total number of records found in result set
value result set from query

Select

By default, the entire set of fields are returned, however, we can whitelist specific fields in the response using $select. Whitelist fields are specified in a comma separated list.

Example request:

GET /odata/assessor?$select=CountyFipsCode,TotalAssessedValue

Example response:


    {
    "@odata.context": "https://insights.cdatalabs.com/$metadata#Assessor",
    "@odata.total_count": 100,
    "@odata.count": 10,
    "@odata.nextlink": "https://insights.cdatalabs.com/odata/assessor/12wqewqeqqxcz4ewogICJGcm9tIjogMTAwLAogICJTaXplIjogMTAwLAogICJUaW1lU3RhbXAiOiA2Mzg1MzA5MTkzMDYzNzA3NjIKfQ==/?$select=CountyFipsCode,TotalAssessedValue"
    "value": [
    {
    "CountyFipsCode": "35043",
    "TotalAssessedValue": 86516
    },
    {
    "CountyFipsCode": "35001",
    "TotalAssessedValue": 103504
    }
    ...
    ]
    }

Top

By default, the first 100 results are returned in any API request, however we can alter the number of results by using the $top option followed by a non-negative integer value.

Example request:

GET /odata/Assessor?$top=1

Recommendation: It is recommended to not exceed $top=100. Exceeding this limit will cause slowness of response payloads

Skip

Often in combination with $top, the $skip option allows for client side paging, also using a non-negative integer we can cycling through the entire result set.

Example request:

GET /odata/Assessor?$top=5&skip=2

Recommendation: It is recommended to use this approach for shallow (small dataset) paging. Using this approach for more than 1 million records will cause slowness in response.

SkipToken

When working with large datasets, our OData API supports pagination to efficiently manage the data retrieval process. This is achieved using the @odata.nextLink property, which provides a URL to the next page of results.

Initial request:

Begin by making a GET request to the desired endpoint. The response will include the first set of results along with '@odata.nextLink' property if more data is available.

Example initial request:

GET /odata/Assessor?$expand=Deeds&$filter=StateOrProvince eq 'CA'

Recommendation: It is recommended to use this approach for paging large datasets.

Checking for '@odata.nextLink'

After receiving the initial response, inspect the JSON payload for the @odata.nextLink property. This property contains the URL for the next page of results. If this property is present, it indicates that there are additional pages of data to retrieve.

Sample JSON response:


    {
    "@odata.context": "https://insights.cdatalabs.com/$metadata#Assessor",
    "@odata.total_count": 100,
    "@odata.count": 100,
    "@odata.nextlink": "https://insights.cdatalabs.com/odata/Assessor/skiptoken/ewogICJGcm9tIjogMTAwLAogICJTaXplIjogMTAwLAogICJUaW1lU3RhbXAiOiA2Mzg1MzE4ODQ2NDM4NzQ4NTYKfQ==/?$expand=Deeds&$filter=StateOrProvince+eq+'CA'",
    "value": [...]
    }
Fetching Subsequent Pages

To retrieve the next set of results, make another HTTP GET request to the URL specified in the '@odata.nextLink' property. Repeat this process, checking each response for a new @odata.nextLink, until this property is no longer present.

Example of fetching next page:

GET https://insights.cdatalabs.com/odata/Assessor/skiptoken/ewogICJGcm9tIjogMTAwLAogICJTaXplIjogMTAwLAogICJUaW1lU3RhbXAiOiA2Mzg1MzE4ODQ2NDM4NzQ4NTYKfQ==/?$expand=Deeds&$filter=StateOrProvince+eq+'CA'
Looping Until All Data is Retrieved

Continue the pagination process in a loop until no @odata.nextLink is found. This ensures that all pages of data are retrieved sequentially.

Best Practices
  1. Error Handling: Implement robust error handling to manage network issues, API rate limits, and other potential errors.
  2. Data Processing: Consider processing each page of data as it is fetched to avoid excessive memory usage.
  3. Respect Rate Limits: Be mindful of the API's rate limits to prevent throttling or blocking.

Filter

Most common option is $filter, typically at least one filter is used when retrieving data, often used are comparison operators to filter results by an MLS (Data Source) via the OriginatingSystemName field.

The following is a list of supported operators and expressions. In future additional OData expressions could be added to the API.

Comparison Operators

Note: single quotation is required for string values

Operator Description Example
eq Equal GET /odata/Assessor?$filter=PostalCode eq '98106'
ne Not Equal GET /odata/Assessor?$filter=AssessmentYear ne '2022'
gt Greater Than GET /odata/Assessor?$filter=AssessedLandValue gt 50000
ge Greater Than or Equal GET /odata/Assessor?$filter=AssessedLandValue ge 50000
lt Less Than GET /odata/Assessor?$filter=AssessedLandValue lt 200000
le Less Than or Equal GET /odata/Assessor?$filter=AssessedLandValue le 200000
in Is a member of GET /odata/Assessor?$filter=StateOrProvince in ('FL')

Boolean Operators

Note: boolean operator is expressed in lower cased

Often used to chain multiple comparison expressions, the boolean operators and and or can precise subset of data

Operator Description Example
and Logical AND GET /odata/Assessor?$filter=PostalCode eq '57043' and CID eq '460000462336'
or Logical OR GET /odata/Assessor?$filter=PostalCode eq '57043' or PostalCode eq '92660'

Expand

In OData, the $expand allows for retrieval of supplementary entity data where there is an association to the primary entity. Typically $expand would be used where Property is the primary EntityType. Expand can retrieve multiple sub-entities using a comma separated list.

Example requests:

GET /odata/Assessor?$expand=Deeds
GET /odata/Assessor?$expand=Deeds,Mortgage

Recommendation: It is recommended to not exceed $top=100 with $expand as $expand increases the payload size and query time, potentially resulting in a degradation of service.

Ordering

To sort results, the $orderby option can be used, providing the desired sort field and direction, if direction is not provided by default it is assumed the sort order is ascending. Multiple order by expressions can be provided by a comma separated list.

Example requests:

GET /odata/Assessor?$orderby=CID
GET /odata/Assessor?$orderby=CID desc
GET /odata/Assessor?$orderby=CID desc, AssessedLandValue asc

API Quotas

APIs can be set on quota usage subscriptions. These are the types of quotas:

  1. API requests on monthly volume
  2. API response record counts (number of records per month)
  3. API response cumulative data size (cumulative data size fetched per month)
  4. API response size limit (10MB response payload limit)
*numbers in the table above are examples only

Response Descriptions

Status Code Error Message Details Example
429 API Calls Quota exceeded. Quota resets midnight on 2025-02-01 This error means the monthly quota of 100000* requests to Insight API has expired. It will reset on 01-Feb-2025 { "StatusCode": 429, "MonitoringNotice": "API Calls Quota exceeded. Quota resets midnight on 2025-02-01" }
429 Record Count Quota limit of 100000* exceeded. Quota resets midnight on 2025-02-01 This error means the monthly quota of number of records/objects for Insight API has expired. It will reset on 01-Feb-2025 { "StatusCode": 429, "MonitoringNotice": "Record Count Quota limit of 100000 exceeded. Quota resets midnight on 2025-02-01" }
413 Volume Quota limit of 10000MBs* exceeded. Quota resets midnight on 2025-02-01 This error means the monthly data volume of 10000MBs* (mega bytes) for Insight API has expired. It will reset on 01-Feb-2025 { "StatusCode": 413, "MonitoringNotice": "Volume Quota limit of 10000MBs exceeded. Quota resets midnight on 2025-02-01" }
413 Response for current request has exceeded the limit, please reduce the request page size via $top or $filter. This error means that response length cannot exceed 10Mbs for Insight API. This is a hard limit. { "StatusCode": 413, "MonitoringNotice": "Response for current request has exceeded the limit, please reduce the request page size via $top or $filter." }

* -> the numbers in the table above are examples.