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:
- "grant_type" = "client_credentials"
- "client_id" = "5pq9999coididi613e222o1nnpp"
- 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
| Field | Type | Description |
|---|---|---|
totalCount | Int! | 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-levelandoror, so all clauses are combined with AND. - Explicit logic: To use OR or mixed logic, structure variables with top-level
andororkeys 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:
StringFiltersupportseq, contains, startswith;IntFilterandFloatFiltersupporteq, gt, ge, lt, le;DateTimeFiltersupportseq, gt, ge, lt, le(ISO date strings). - Enums:
propertyTypeandpropertySubTypeuse GraphQL enums (e.g.Residential,SingleFamilyResidence) - pass them without wrapping ineqat the filter field. - OData mapping: GraphQL
cidmaps to ODataCID;dates.modificationTimeStampmaps to ODatamodificationTimestamp.
| BasePropertyFilter field | Purpose |
|---|---|
cid, countyFipsCode, parcelNumber | Identify or constrain parcel keys. |
location | BaseLocationFilter -> address (city, postalCode, unparsedAddress, …). |
propertyType, propertySubType | Enumerated property classification. |
characteristics | Lot size and related numeric filters. |
structure | Bed/bath counts, living area, year built, stories. |
latestTransfer | Sale price and contract/sale date filters. |
estimatedValue | AVM estimate thresholds. |
dates | BaseDatesFilter -> modificationTimeStamp for record change time. |
How sorting works
Use optional orderBy: BasePropertySort. Only certain leaf fields are mapped to OData $orderby:
| Group | Sortable fields -> OData |
|---|---|
characteristics | lotSizeSquareFeet |
structure | bedroomsTotal, bathroomsTotalInteger, livingArea |
latestTransfer | salesPrice, recordingDate |
estimatedValue | estimate |
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 underBaseProperty, the resolver loads linked MLS/RESO listings for the same CID (batched per request). Omit this field to avoid that work.
Pagination
| Argument | Maps to OData | Description |
|---|---|---|
limit | $top | Maximum rows returned for this call. |
offset | $skip | Rows to skip from the start of the filtered set. |
Operators (filter leaf inputs)
| Operator | Typical input types | Meaning |
|---|---|---|
eq | String, Int, date string | Equal. |
contains, startswith | String | Substring / prefix (string filters). |
gt, ge, lt, le | Numeric / date | Range comparisons. |
BaseProperty - root fields (schema)
| Field | Description |
|---|---|
cid | Constellation property identifier (U.S.). |
countyFipsCode | 5-digit state + county FIPS. |
parcelNumber | County assessor parcel id. |
modificationTimestamp | Last modification time for the base record. |
propertyType, propertySubType | General and detailed property classification strings. |
location | BaseLocation: address, census geographies, GIS + optional parcel boundary. |
characteristics | BaseCharacteristics: lot, topography, views, pool, waterfront, community features, etc. |
structure | BaseStructure: rooms, area, year built, construction, garage, heating/cooling, etc. |
tax | BaseTax: assessed values, tax amounts, legal description, zoning, PLSS, subdivision. |
occupantOwner | BaseOccupantOwner: owner names, mailing address, occupant type. |
latestTransfer | BaseLatestTransfer: deed/sale parties, price, dates, REO flags. |
currentLoans | BaseCurrentLoans: summary plus up to five lien slots. |
estimatedValue | BaseEstimatedValue: AVM estimate, range, confidence. |
resoProperties | List 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.*)
| Field | Description |
|---|---|
id | Census boundary identifier stored on the base property record. |
name | Boundary display name from CensusBoundary (for example county/parish name). |
stateOrProvince | State or province code associated with the boundary. |
country | Country code for the boundary (typically US). |
type | Census boundary classification (for example Census County or Parish, Census Designated Place). |
geom | Boundary geometry payload (GeoJSON-like structure). |
latitude, longitude | Representative coordinates for the boundary. |
version | Boundary 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
| Field | Type | Description |
|---|---|---|
totalCount | Int! | 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:
RESOPropertyFilterwith top-levelcid,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 RESOStringFilterwitheq,ne,contains,startsWithas defined on that schema. - Operators: Strings use comparison filters as noted above; numeric fields use
FloatFilterandIntFilter; dates useDateTimeFilterwitheq, gt, ge, lt, le. - Combining: Same as base properties: omit top-level
andororfor 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 branch | Role |
|---|---|
cid, listingKey | Pin or scan specific listings / linked public CID. |
propertyType | RESO string filter on listing property type. |
location | Address-centric geographic filters. |
listing | MLS workflow, pricing, dates, agents, equipment, lease, structure hints. |
How sorting works
orderBy: RESOPropertySort supports nested groups:
| Group | Fields |
|---|---|
price | closePrice, listPrice, listPriceLow, originalListPrice, previousListPrice |
dates | modificationTimestamp (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
RESOPropertygroups: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)
| Field | GraphQL type | Summary |
|---|---|---|
cid, listingKey, propertyType, propertySubType | Scalars / strings | Identification and classification. |
listing | RESOListingGroup | Status, pricing, dates, agents, remarks, media hooks. |
location | RESOLocationGroup | Address, schools, GIS, parcel context. |
structure, tax, financial, … | RESO groups | Building details, tax roll, finance, HOA, equipment, utilities, etc. |
baseProperty | BaseProperty | Linked assessor/public-record profile. |
See the GraphQL schema / introspection for the full list of leaf fields inside each group.
Pagination
| Argument | Role |
|---|---|
limit | Page size (maps to backend top/skip semantics). |
offset | Rows 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
| Field | Type | Description |
|---|---|---|
totalCount | Int! | 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
SchoolFiltermaps to a single OData property (e.g.city,studentsTotal,latitude). There is nolocation.address.citypath in the filter - usecitydirectly. - Operators: Same primitives as elsewhere:
StringFilter(eq, contains, startswith), plusIntFilter,FloatFilter, andBooleanFilterfor fields such ascharterYN. - Combining: Resolver wraps plain objects in
{ and: ... }when you omit explicitandoror. - Strings in OData: Insights double-quotes certain school string literals for OData compatibility - handled inside the API client.
| SchoolFilter (examples) | Purpose |
|---|---|
id, districtId, name, status | Identity and lifecycle. |
unparsedAddress, city, stateOrProvince, postalCode, … | Address matching. |
countyFipsCode, countyOrParish | Geography filters. |
level, gradeRangeMin, gradeRangeMax | Grade span. |
charterYN, magnetYN, privateYN, virtualYN | Program flags. |
studentsTotal, studentTeacherRatio, graduationRate, … | Numeric metrics. |
latitude, longitude | Geo bounds / proximity filters. |
How sorting works
SchoolSort exposes top-level fields only. Mapped OData columns include:
| Sort field | Notes |
|---|---|
name, city, stateOrProvince, status | Lexical / status ordering. |
studentsTotal | Enrollment size. |
graduationRate | School-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 loadsSchoolDistrictviadistrictId+ DataLoader (batched).testScores: Loads TestScores rows whereschoolIdequals this school’sid.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)
| Field | Description / child type |
|---|---|
id, name, status, modificationTimestamp | Core identifiers and freshness. |
location | SchoolLocation -> address (SchoolAddress), gis (SchoolGIS). |
contact | SchoolContact (website, phone). |
classification | SchoolClassification (level, grades, charter/magnet/private/virtual flags). |
enrollment | SchoolEnrollment (counts, ratios, demographics). |
spending | SchoolSpending (per-pupil and federal/state splits). |
graduation | SchoolGraduation (rate + year). |
district | Optional 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
| Field | Type | Description |
|---|---|---|
totalCount | Int! | 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
SchoolDistrictFiltermap 1:1 to OData properties (e.g.name,city,districtRating,numberElementarySchools). - Operators: Identical vocabulary to schools (
eq, contains, startswith, plus numeric comparisons). - Combining: Implicit
andwrapping whenandororis omitted at the top level.
| SchoolDistrictFilter (examples) | Purpose |
|---|---|
id, name, status | Identity. |
unparsedAddress, city, stateOrProvince, postal fields | District office address. |
countyFipsCode, countyOrParish | Regional filters. |
gradeRangeMin, gradeRangeMax | District-wide grade span. |
numberElementarySchools, numberMiddleSchools, numberHighSchools | Composition counts. |
districtRating, districtRank, districtRankOf, districtRankYear | Accountability metrics. |
graduationRate, graduationRateYear | District graduation filters. |
How sorting works
| SchoolDistrictSort field | Typical use |
|---|---|
name, city, stateOrProvince, status | Lexical ordering. |
districtRating, districtRank | Performance ordering. |
graduationRate | Outcome ordering. |
How expansion works
schools: Batch-fetches allSchoolrows whosedistrictIdequals this district’sid(large result sets possible - use pagination on the parent query and narrow filters).testScores: TestScores joined withschoolIdequal to the district id (same identifier space as documented onTestScore).
SchoolDistrict - fields by level (schema)
| Field | Description / child type |
|---|---|
id, name, status, modificationTimestamp | Core identifiers. |
location | SchoolDistrictLocation -> address, gis. |
composition | SchoolDistrictComposition (grade span, school counts). |
ratings | SchoolDistrictRatings. |
graduation | SchoolDistrictGraduation. |
schools | [School!]! - member schools (expanded query). |
testScores | [TestScore!]! - linked assessments. |
Pagination
Same as schools: limit maps to OData $top, and offset maps to $skip.