Constellation Data Labs
Constellation Data Labs | Insights API (GraphQL)
Constellation Data Labs

API Documentation (GraphQL)

The Insights GraphQL API combines the RESO Listings with the Insights OData APIs to provide a combined result set.

Interactive GraphiQL: Open the GraphiQL Explorer to run and validate the example GraphQL queries shown in this documentation.

This API uses GraphQL, which is a query language with the ability to define precisely the data you want to fetch

The GraphQL query is resolving our Listings API (MLS data) and our Insights API (Public Records) API in a single call.

Learn how to use GraphQL with How to use GraphQL

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.

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"
}

Base properties (baseProperties)

The Insights Base Property query (baseProperties) returns U.S. assessor and public-record style property rows from the BaseProperties OData entity. Each row is keyed by CID together with county FIPS and parcel context. The GraphQL schema groups attributes into objects such as location, characteristics, structure, tax, latestTransfer, currentLoans, and estimatedValue.

Connection result

FieldTypeDescription
totalCountInt!Count of records matching the filter (from OData where available).
nodes[BaseProperty]Page of BaseProperty objects for the current limit and offset.

Endpoint

POST /graphql

Root query (example)

query BasePropertyQuery(
  $filter: BasePropertyFilter!
  $limit: Int
  $offset: Int
  $orderBy: BasePropertySort
) {
  baseProperties(filter: $filter, limit: $limit, offset: $offset, orderBy: $orderBy) {
    totalCount
    nodes {
      cid
      countyFipsCode
      parcelNumber
      modificationTimestamp
      propertyType
      propertySubType
      location {
        address { unparsedAddress city stateOrProvince postalCode }
        census {
          administrative {
            countyOrParish {
              id
              name
              stateOrProvince
              country
              type
              geom
              latitude
              longitude
              version
            }
          }
        }
        gis { latitude longitude }
      }
      characteristics { lotSizeSquareFeet view poolFeatures }
      structure {
        bedroomsTotal
        bathroomsTotalInteger
        livingArea
        yearBuilt
      }
      latestTransfer { salesPrice recordingDate }
      estimatedValue { estimate confidenceScore }
      resoProperties { listingKey }
    }
  }
}

Variables (example)

{
  "filter": {
    "countyFipsCode": { "eq": "06037" },
    "structure": { "bedroomsTotal": { "ge": 3 }, "livingArea": { "ge": 1500 } },
    "estimatedValue": { "estimate": { "lt": 1000000 } },
    "dates": { "modificationTimeStamp": { "ge": "2026-01-01" } }
  },
  "limit": 10,
  "offset": 0,
  "orderBy": {
    "latestTransfer": { "recordingDate": "DESC" },
    "estimatedValue": { "estimate": "ASC" }
  }
}

How filtering works

Arguments use the input type BasePropertyFilter. The server forwards filters to Insights as an OData $filter string.

  • Flat vs nested: You may pass a single “flat” object of conditions (e.g. countyFipsCode + structure). The resolver wraps it as { and: filter } when you do not supply top-level and or or, so all clauses are combined with AND.
  • Explicit logic: To use OR or mixed logic, structure variables with top-level and or or keys whose values are objects of field filters (same pattern as other Insights-backed queries).
  • Nested sections: Filters mirror the schema: e.g. location.address.city, structure.livingArea, latestTransfer.salesPrice, estimatedValue.estimate, dates.modificationTimeStamp.
  • Operators: StringFilter supports eq, contains, startswith; IntFilter and FloatFilter support eq, gt, ge, lt, le; DateTimeFilter supports eq, gt, ge, lt, le (ISO date strings).
  • Enums: propertyType and propertySubType use GraphQL enums (e.g. Residential, SingleFamilyResidence) - pass them without wrapping in eq at the filter field.
  • OData mapping: GraphQL cid maps to OData CID; dates.modificationTimeStamp maps to OData modificationTimestamp.
BasePropertyFilter fieldPurpose
cid, countyFipsCode, parcelNumberIdentify or constrain parcel keys.
locationBaseLocationFilter -> address (city, postalCode, unparsedAddress, …).
propertyType, propertySubTypeEnumerated property classification.
characteristicsLot size and related numeric filters.
structureBed/bath counts, living area, year built, stories.
latestTransferSale price and contract/sale date filters.
estimatedValueAVM estimate thresholds.
datesBaseDatesFilter -> modificationTimeStamp for record change time.

How sorting works

Use optional orderBy: BasePropertySort. Only certain leaf fields are mapped to OData $orderby:

GroupSortable fields -> OData
characteristicslotSizeSquareFeet
structurebedroomsTotal, bathroomsTotalInteger, livingArea
latestTransfersalesPrice, recordingDate
estimatedValueestimate

Each sort direction is ASC or DESC (SortOrder enum). Multiple groups can appear in one orderBy object; the server builds a comma-separated OData order list.

How expansion works

In GraphQL, “expansion” means requesting nested fields in the query. There are no separate REST-style expand parameters.

  • Nested objects: Ask for location { address census gis }, tax { … }, occupantOwner { … }, etc. Null objects appear when the source has no data.
  • location.gis.parcel: Resolved via DataLoader / ParcelBoundary API by CID (extra round-trip when selected).
  • resoProperties: When listed under BaseProperty, the resolver loads linked MLS/RESO listings for the same CID (batched per request). Omit this field to avoid that work.

Pagination

ArgumentMaps to ODataDescription
limit$topMaximum rows returned for this call.
offset$skipRows to skip from the start of the filtered set.

Operators (filter leaf inputs)

OperatorTypical input typesMeaning
eqString, Int, date stringEqual.
contains, startswithStringSubstring / prefix (string filters).
gt, ge, lt, leNumeric / dateRange comparisons.

BaseProperty - root fields (schema)

FieldDescription
cidConstellation property identifier (U.S.).
countyFipsCode5-digit state + county FIPS.
parcelNumberCounty assessor parcel id.
modificationTimestampLast modification time for the base record.
propertyType, propertySubTypeGeneral and detailed property classification strings.
locationBaseLocation: address, census geographies, GIS + optional parcel boundary.
characteristicsBaseCharacteristics: lot, topography, views, pool, waterfront, community features, etc.
structureBaseStructure: rooms, area, year built, construction, garage, heating/cooling, etc.
taxBaseTax: assessed values, tax amounts, legal description, zoning, PLSS, subdivision.
occupantOwnerBaseOccupantOwner: owner names, mailing address, occupant type.
latestTransferBaseLatestTransfer: deed/sale parties, price, dates, REO flags.
currentLoansBaseCurrentLoans: summary plus up to five lien slots.
estimatedValueBaseEstimatedValue: AVM estimate, range, confidence.
resoPropertiesList of RESOProperty for this CID (requires resolver expansion).

Nested highlights: location.address (full addressing components); location.census (administrative, legislative, statistical GeoIDs and boundary details); location.gis (lat/long, precision, parcel boundary); latestTransfer.buyers/sellers arrays; currentLoans.loans array of BaseLoan.

CensusGeo fields (used under location.census.*)

FieldDescription
idCensus boundary identifier stored on the base property record.
nameBoundary display name from CensusBoundary (for example county/parish name).
stateOrProvinceState or province code associated with the boundary.
countryCountry code for the boundary (typically US).
typeCensus boundary classification (for example Census County or Parish, Census Designated Place).
geomBoundary geometry payload (GeoJSON-like structure).
latitude, longitudeRepresentative coordinates for the boundary.
versionBoundary data version year.

Example response (abbreviated)

{
  "data": {
    "baseProperties": {
      "totalCount": 1,
      "nodes": [{
        "cid": "06037123456",
        "countyFipsCode": "06037",
        "parcelNumber": "1234-567-890",
        "modificationTimestamp": "2026-01-10",
        "propertyType": "Residential",
        "structure": { "bedroomsTotal": 4, "livingArea": 2500 },
        "estimatedValue": { "estimate": 1012000, "confidenceScore": 95 }
      }]
    }
  }
}

RESO listings (resoProperties)

The resoProperties query returns MLS listing rows modeled as RESO-style groups (listing, location, structure, tax, financial, etc.). Filters are translated to OData against the RESO listings endpoint; results expose rich nested objects on each RESOProperty.

Connection result

FieldTypeDescription
totalCountInt!Matching listing count when returned by the backing API.
nodes[RESOProperty]Listings for the current page.

Endpoint

POST /graphql

Root query (example)

query ResoListings(
  $filter: RESOPropertyFilter!
  $limit: Int
  $offset: Int
  $orderBy: RESOPropertySort
) {
  resoProperties(filter: $filter, limit: $limit, offset: $offset, orderBy: $orderBy) {
    totalCount
    nodes {
      cid
      listingKey
      propertyType
      propertySubType
      listing {
        standardStatus
        listPrice
        closePrice
      }
      location {
        address {
          unparsedAddress
          city
          stateOrProvince
          postalCode
        }
      }
      baseProperty {
        countyFipsCode
        parcelNumber
      }
    }
  }
}

Variables (example)

{
  "filter": {
    "listing": {
      "standardStatus": { "eq": "Active" }
    },
    "location": {
      "address": {
        "stateOrProvince": { "eq": "CA" }
      }
    }
  },
  "limit": 10,
  "offset": 0,
  "orderBy": {
    "price": { "listPrice": "DESC" },
    "dates": { "modificationTimestamp": "DESC" }
  }
}

How filtering works

  • Input type: RESOPropertyFilter with top-level cid, listingKey, propertyType, location, listing.
  • Listing subtree: listing.originatingSystemName, listing.standardStatus, listing.price (close/list/previous prices), listing.dates (status/modification/close/on-market/off-market timestamps, days on market), listing.agentOffice (buyer/list/co agents & offices), listing.structure, listing.equipment, listing.leaseAmount, listing.rawMlsPropertySubType.
  • Location subtree: location.address.* for street, city, state, postal, county, etc. - each uses RESO StringFilter with eq, ne, contains, startsWith as defined on that schema.
  • Operators: Strings use comparison filters as noted above; numeric fields use FloatFilter and IntFilter; dates use DateTimeFilter with eq, gt, ge, lt, le.
  • Combining: Same as base properties: omit top-level and or or for an implicit AND bundle, or supply explicit logical objects when your client passes structured filters.
  • Backend: Built server-side into OData for the Property/listings service (not the same entity set as BaseProperties).
RESOPropertyFilter branchRole
cid, listingKeyPin or scan specific listings / linked public CID.
propertyTypeRESO string filter on listing property type.
locationAddress-centric geographic filters.
listingMLS workflow, pricing, dates, agents, equipment, lease, structure hints.

How sorting works

orderBy: RESOPropertySort supports nested groups:

GroupFields
priceclosePrice, listPrice, listPriceLow, originalListPrice, previousListPrice
datesmodificationTimestamp (and other date sorts if enabled in schema)

Use ASC or DESC per field. Only mapped fields are sent to the listings OData layer.

How expansion works

  • Select any subset of RESOProperty groups: business, characteristics, equipment, farming, financial, hoa, listing, location, occupantOwner, structure, tax, unitTypes, utilities, identifiers (universalPropertyId, etc.).
  • baseProperty: Joins to Insights Base Property by CID - include only when you need assessor/public-record fields to avoid an extra Insights fetch per listing.

RESOProperty - root groups (schema)

FieldGraphQL typeSummary
cid, listingKey, propertyType, propertySubTypeScalars / stringsIdentification and classification.
listingRESOListingGroupStatus, pricing, dates, agents, remarks, media hooks.
locationRESOLocationGroupAddress, schools, GIS, parcel context.
structure, tax, financial, …RESO groupsBuilding details, tax roll, finance, HOA, equipment, utilities, etc.
basePropertyBasePropertyLinked assessor/public-record profile.

See the GraphQL schema / introspection for the full list of leaf fields inside each group.

Pagination

ArgumentRole
limitPage size (maps to backend top/skip semantics).
offsetRows to skip.

Schools (schools)

The schools query reads the Insights Schools OData entity. Each School node is one school record; filters are flat OData column names (no nested filter objects like Base Property).

Connection result

FieldTypeDescription
totalCountInt!Row count for the filter.
nodes[School]School records for this page.

Endpoint

POST /graphql

Root query (example)

query SchoolsQuery(
  $filter: SchoolFilter!
  $limit: Int
  $offset: Int
  $orderBy: SchoolSort
) {
  schools(filter: $filter, limit: $limit, offset: $offset, orderBy: $orderBy) {
    totalCount
    nodes {
      id
      name
      status
      modificationTimestamp
      location {
        address { city stateOrProvince postalCode }
        gis { countyFipsCode latitude longitude }
      }
      classification { level gradeRangeMin gradeRangeMax charterYN }
      enrollment { studentsTotal studentTeacherRatio }
      district { id name status }
      testScores { testYear subjectName percentMetStandard }
      schoolRatings { schoolRating schoolRank schoolRankYear }
    }
  }
}

Variables (example)

{
  "filter": {
    "stateOrProvince": { "eq": "CA" },
    "city": { "contains": "Los Angeles" }
  },
  "limit": 25,
  "offset": 0,
  "orderBy": { "name": "ASC" }
}

How filtering works

  • Flat filters: Every key on SchoolFilter maps to a single OData property (e.g. city, studentsTotal, latitude). There is no location.address.city path in the filter - use city directly.
  • Operators: Same primitives as elsewhere: StringFilter (eq, contains, startswith), plus IntFilter, FloatFilter, and BooleanFilter for fields such as charterYN.
  • Combining: Resolver wraps plain objects in { and: ... } when you omit explicit and or or.
  • Strings in OData: Insights double-quotes certain school string literals for OData compatibility - handled inside the API client.
SchoolFilter (examples)Purpose
id, districtId, name, statusIdentity and lifecycle.
unparsedAddress, city, stateOrProvince, postalCode, …Address matching.
countyFipsCode, countyOrParishGeography filters.
level, gradeRangeMin, gradeRangeMaxGrade span.
charterYN, magnetYN, privateYN, virtualYNProgram flags.
studentsTotal, studentTeacherRatio, graduationRate, …Numeric metrics.
latitude, longitudeGeo bounds / proximity filters.

How sorting works

SchoolSort exposes top-level fields only. Mapped OData columns include:

Sort fieldNotes
name, city, stateOrProvince, statusLexical / status ordering.
studentsTotalEnrollment size.
graduationRateSchool-level graduation metric.

Directions: ASC or DESC. Only one field per group is typical; multiple entries depend on server mapping (comma-separated OData).

How expansion works

  • district: Resolver loads SchoolDistrict via districtId + DataLoader (batched).
  • testScores: Loads TestScores rows where schoolId equals this school’s id.
  • schoolRatings: Loads accountability/rating rows for the school id.
  • Omit these fields in the GraphQL selection set when you do not need them to reduce Insights calls.

School - fields by level (schema)

FieldDescription / child type
id, name, status, modificationTimestampCore identifiers and freshness.
locationSchoolLocation -> address (SchoolAddress), gis (SchoolGIS).
contactSchoolContact (website, phone).
classificationSchoolClassification (level, grades, charter/magnet/private/virtual flags).
enrollmentSchoolEnrollment (counts, ratios, demographics).
spendingSchoolSpending (per-pupil and federal/state splits).
graduationSchoolGraduation (rate + year).
districtOptional SchoolDistrict parent.
testScores[TestScore!]! - assessment outcome rows.
schoolRatings[SchoolRating!]! - rating/rank rows.

Pagination

limit maps to OData $top, and offset maps to $skip.

School districts (schoolDistricts)

The schoolDistricts query reads the Insights SchoolDistricts OData entity. Filters use flat district columns; expansions load related schools and test rows by foreign key.

Connection result

FieldTypeDescription
totalCountInt!District rows matching the filter.
nodes[SchoolDistrict]District records for this page.

Endpoint

POST /graphql

Root query (example)

query SchoolDistrictsQuery(
  $filter: SchoolDistrictFilter!
  $limit: Int
  $offset: Int
  $orderBy: SchoolDistrictSort
) {
  schoolDistricts(
    filter: $filter
    limit: $limit
    offset: $offset
    orderBy: $orderBy
  ) {
    totalCount
    nodes {
      id
      name
      status
      modificationTimestamp
      location {
        address { city stateOrProvince postalCode }
        gis { countyFipsCode countyOrParish }
      }
      composition {
        gradeRangeMin
        gradeRangeMax
        numberElementarySchools
        numberMiddleSchools
        numberHighSchools
      }
      ratings { districtRating districtRank districtRankYear }
      graduation { graduationRate graduationRateYear }
      schools { id name level status }
      testScores { testYear subjectName percentMetStandard }
    }
  }
}

Variables (example)

{
  "filter": {
    "stateOrProvince": { "eq": "TX" },
    "name": { "contains": "Independent" }
  },
  "limit": 10,
  "offset": 0,
  "orderBy": { "name": "ASC" }
}

How filtering works

  • Flat filters: Keys on SchoolDistrictFilter map 1:1 to OData properties (e.g. name, city, districtRating, numberElementarySchools).
  • Operators: Identical vocabulary to schools (eq, contains, startswith, plus numeric comparisons).
  • Combining: Implicit and wrapping when and or or is omitted at the top level.
SchoolDistrictFilter (examples)Purpose
id, name, statusIdentity.
unparsedAddress, city, stateOrProvince, postal fieldsDistrict office address.
countyFipsCode, countyOrParishRegional filters.
gradeRangeMin, gradeRangeMaxDistrict-wide grade span.
numberElementarySchools, numberMiddleSchools, numberHighSchoolsComposition counts.
districtRating, districtRank, districtRankOf, districtRankYearAccountability metrics.
graduationRate, graduationRateYearDistrict graduation filters.

How sorting works

SchoolDistrictSort fieldTypical use
name, city, stateOrProvince, statusLexical ordering.
districtRating, districtRankPerformance ordering.
graduationRateOutcome ordering.

How expansion works

  • schools: Batch-fetches all School rows whose districtId equals this district’s id (large result sets possible - use pagination on the parent query and narrow filters).
  • testScores: TestScores joined with schoolId equal to the district id (same identifier space as documented on TestScore).

SchoolDistrict - fields by level (schema)

FieldDescription / child type
id, name, status, modificationTimestampCore identifiers.
locationSchoolDistrictLocation -> address, gis.
compositionSchoolDistrictComposition (grade span, school counts).
ratingsSchoolDistrictRatings.
graduationSchoolDistrictGraduation.
schools[School!]! - member schools (expanded query).
testScores[TestScore!]! - linked assessments.

Pagination

Same as schools: limit maps to OData $top, and offset maps to $skip.