# AnyProp RESO Listings API Documentation ## Introduction Welcome to the AnyProp RESO Listings API documentation. Our API provides access to real estate listing data following the [Real Estate Standards Organization (RESO)](https://www.reso.org/reso-web-api/) Web API standards. We utilize [OData (Open Data Protocol)](https://www.odata.org/) to offer a flexible and powerful querying capability for our data resources. ## Authentication All API calls (except the token endpoint) require a Bearer token in the Authorization header: ```http Authorization: Bearer your_access_token ``` ### Getting an Access Token **POST /v1/token** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) ## Account Resources, Settings, and Permission You can view account settings and permissions on the account/resources endpoint. **GET /v1/account/resources** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) ## MLS Profiles You can get mls profiles (including logos, disclaimers, and basic compliance elements). **GET /v1/listings/mls_profiles** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) ## API Resources The base URL for all listings API requests is: ``` https://api.anyprop.com/v1/listings/data ``` ### Property Resource **GET /v1/listings/data/Property** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) **Available `$filter` fields:** `OriginatingSystemName, ModificationTimestamp, PhotosChangeTimestamp, StandardStatus, PropertyType, ListingId, ListOfficeMlsId, ListingKey, OnMarketTimestamp, ListingContractDate, OnMarketDate, ListPrice, OriginalListPrice, CloseDate, StatusChangeTimestamp, MlsStatus, City, PostalCode, PostalCodePlus4, StateOrProvince, StreetDirPrefix, StreetDirSuffix, StreetName, StreetNumber, StreetSuffix, UnitNumber, UnparsedAddress, ListAgentFullName, CoListAgentFullName, BuyerAgentFullName, CoBuyerAgentFullName, ListOfficeName, CoListOfficeName, BuyerOfficeName, CoBuyerOfficeName, ListAgentEmail, CoListAgentEmail, BuyerAgentEmail, CoBuyerAgentEmail, BathroomsTotalInteger, PropertySubType, BathroomsPartial, BathroomsHalf, BathroomsFull, LotSizeArea, LotSizeSquareFeet, Latitude, Longitude, GarageYN, GarageSpaces, CarportYN, CarportSpaces, PoolPrivateYN, SeniorCommunityYN, AssociationYN, NewConstructionYN, BedroomsTotal, DaysOnMarket, YearBuiltEffective, YearBuilt, PhotosCount, LivingArea, SpecialListingConditions, CountyOrParish, OriginalEntryTimestamp, RawLocation, Location, RawAddressSearchText, ListAgentSearchText, BuyerAgentSearchText, ListOfficeSearchText, BuyerOfficeSearchText, KeywordSearchText, anyprop_ListAgentEmail, anyprop_BuyerAgentEmail, anyprop_ListAgentPhone, anyprop_BuyerAgentPhone, anyprop_OriginalEntryTimestamp, anyprop_UpdatedTimestamp, anyprop_DaysOnMarketReferenceTimestamp, anyprop_Address, anyprop_AddressAlias` **`$expand` options:** - `Media` - Property media (join from the Media resource) - `OpenHouse` - Property open houses (join from the OpenHouse resource) - `PropertyChange` - (Non-RESO) Historical change tracking for property fields with old/new values and modification timestamps - `PropertyRooms` - Property room details (join from the PropertyRooms resource) - `PropertyUnitTypes` - Property unit type details (join from the PropertyUnitTypes resource) - `PreferredMedia` - (Non-RESO) Optimized expand that returns the first 2 photos by media order, with priority given to the preferred photo (much faster than expanding Media) **Geospatial Filtering:** Supported via `geo.distance` on **RawLocation** or **Location**. Only `le` comparison is accepted (distance in meters): ```http $filter=geo.distance(RawLocation, geography'POINT(-82.98163737 35.11490043)') le 1000 ``` Polygon filtering is supported via `geo.intersects` on **RawLocation** or **Location**: ```http $filter=geo.intersects(RawLocation, geography'POLYGON((-80.30 25.70, -80.13 25.70, -80.13 25.85, -80.30 25.85, -80.30 25.70))') ``` **Text Search:** The API supports fuzzy text matching using the `search.text` function. This feature allows you to search for properties using partial or incomplete text across multiple fields. Syntax: ``` $filter=search.text(field, 'search terms', min_matches) ``` Parameters: - **field** - The search field to query (e.g., `RawAddressSearchText`, `KeywordSearchText`) - **search terms** - The text to search for (as a string) - **min_matches** (optional) - Required number of matching terms. For example, `2` means at least 2 of the tokenized search terms must match. To require all terms to match, use a large number like `1000` (values exceeding the token count are automatically capped). Default behavior when `min_matches` is omitted: - **RawAddressSearchText**: Matches if at least 40% of terms match - **All other fields**: Strict matching (all terms must match) **Address Search:** Search for properties using partial or incomplete addresses with the **RawAddressSearchText** field. _Note: This performs fuzzy text matching only; it is **not** a geocoding service._ Example: ``` # Search for addresses containing "100 Gold" (defaults to 40% match) $filter=search.text(RawAddressSearchText,'100 Gold') # Search for properties on Main Street (require all terms) $filter=search.text(RawAddressSearchText,'Main Street',1000) ``` **Agent and Office Search:** Search for properties by agent, co-agent, office, or co-office names using `search.text` on the following fields: - **ListAgentSearchText** - Listing agent names - **BuyerAgentSearchText** - Buyer agent names - **ListOfficeSearchText** - Listing office names - **BuyerOfficeSearchText** - Buyer office names Example: ``` # Search for properties with listing agent "John Smith" (strict match) $filter=search.text(ListAgentSearchText,'John Smith') # Search for properties with buyer agent "Jane Doe" $filter=search.text(BuyerAgentSearchText,'Jane Doe') # Search for properties with listing office "Acme Realty" $filter=search.text(ListOfficeSearchText,'Acme Realty') ``` **Keyword Search:** Search across listing remarks and descriptions using the **KeywordSearchText** field. Fields included in search: `ListingId`, `Appliances`, `ArchitecturalStyle`, `Heating`, `Cooling`, `InteriorFeatures`, `ParkingFeatures`, `PropertySubType`, `WaterfrontFeatures`, `View`, `PublicRemarks` Example: ``` # Search for properties mentioning "pool" and "renovated" (strict match) $filter=search.text(KeywordSearchText,'pool renovated') # Search for properties with at least one of these terms $filter=search.text(KeywordSearchText,'waterfront dock boat',1) ``` **Sorting by Relevance:** To sort results by the best match (using BM25), use: ``` $orderby=relevance desc ``` Complete Example: ``` # Search for "100 Gold" and sort by best match $filter=search.text(RawAddressSearchText,'100 Gold')&$orderby=relevance desc ``` **Internal Non-RESO Fields:** Useful fields derived internally that extend beyond the RESO standard: _Geocoded Address:_ - **anyprop_Address** - Normalized internal address (geocoded). Useful for identifying properties across listings. - **anyprop_AddressAlias** - Slug derived from `anyprop_Address`. Useful for identifying properties across listings. - **anyprop_RawAddress** - Raw full address built from MLS address fields, the unparsed address, and any other available information - **anyprop_HouseNumber** - House number from the geocoded address - **anyprop_StreetName** - Street name from the geocoded address - **anyprop_StreetAddress** - Street address (house number + street name) from the geocoded address - **anyprop_City** - City from the geocoded address - **anyprop_State** - State from the geocoded address - **anyprop_ZipCode** - Zip code from the geocoded address _Geolocation:_ - **anyprop_Latitude** - Geocoded latitude - **anyprop_Longitude** - Geocoded longitude - **anyprop_AddressFoundYN** - Whether the address was successfully geocoded - **anyprop_ExactGeocodeYN** - Whether the geocode is an exact match (address accuracy greater than or equal to line interpolation) _Timestamps:_ - **anyprop_OriginalEntryTimestamp** - Derived OriginalEntryTimestamp based on internal data. Example: `2025-08-19T16:19:13.000000Z` - **anyprop_DaysOnMarketReferenceTimestamp** - Derived date used to calculate DaysOnMarket. The DaysOnMarket value is computed as: `Current Time - anyprop_DaysOnMarketReferenceTimestamp`. Available as a $filter field. Example: `2026-02-17T22:15:04.300000Z` _Financial:_ - **anyprop_AssociationFeeTotalMonthly** - Derived monthly association fee total. Example: `250` _Agent & Contact Search:_ - **anyprop_ListAgentEmail** - Searches both list agent and co-list agent email addresses. Use standard comparison operators (`eq`, `contains`, etc.) — not a text search field. - **anyprop_BuyerAgentEmail** - Searches both buyer agent and co-buyer agent email addresses. Use standard comparison operators (`eq`, `contains`, etc.) — not a text search field. - **anyprop_ListAgentPhone** - Searches both list agents (main + co) across all phone number fields: `ListAgentDirectPhone`, `ListAgentHomePhone`, `ListAgentMobilePhone`, `ListAgentOfficePhone`, `ListAgentPreferredPhone`, `ListAgentTollFreePhone`, `CoListAgentDirectPhone`, `CoListAgentHomePhone`, `CoListAgentMobilePhone`, `CoListAgentOfficePhone`, `CoListAgentPreferredPhone`, `CoListAgentTollFreePhone` - **anyprop_BuyerAgentPhone** - Searches both buyer agents (main + co) across all phone number fields: `BuyerAgentDirectPhone`, `BuyerAgentHomePhone`, `BuyerAgentMobilePhone`, `BuyerAgentOfficePhone`, `BuyerAgentPreferredPhone`, `BuyerAgentTollFreePhone`, `CoBuyerAgentDirectPhone`, `CoBuyerAgentHomePhone`, `CoBuyerAgentMobilePhone`, `CoBuyerAgentOfficePhone`, `CoBuyerAgentPreferredPhone`, `CoBuyerAgentTollFreePhone` > **Note:** Phone number fields can be queried in either the raw form from the MLS or a "normalized" form (all numeric). ### Media Resource **GET /v1/listings/data/Media** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) **Available `$filter` fields:** `OriginatingSystemName, ModificationTimestamp, MediaKey, MediaStatus, Order, MediaCategory, ResourceName, ResourceRecordKey, ResourceRecordID` ### Member Resource **GET /v1/listings/data/Member** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) **Available `$filter` fields:** `OriginatingSystemName, ModificationTimestamp, MemberMlsId, MemberFullName, MemberEmail, MemberStatus, MemberStateOrProvince, MemberStateLicense, OfficeKey, OfficeMlsId, OfficeName, anyprop_MemberPhone` **Internal Non-RESO Fields:** - **anyprop_MemberPhone** - Searches across all member phone number fields: `MemberDirectPhone`, `MemberHomePhone`, `MemberMobilePhone`, `MemberOfficePhone`, `MemberPreferredPhone`, `MemberTollFreePhone` > **Note:** Phone number fields can be queried in either the raw form from the MLS or a "normalized" form (all numeric). ### Office Resource **GET /v1/listings/data/Office** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) **Available `$filter` fields:** `OriginatingSystemName, ModificationTimestamp, OfficeMlsId, OfficeName, OfficePhone, OfficeEmail, OfficeStateOrProvince, OfficeStatus` ### OpenHouse Resource **GET /v1/listings/data/OpenHouse** [OpenAPI specification](https://doc-dev-anyprop.anyprop.com/specs/v1-listings-openapi.json) **Available `$filter` fields:** `OriginatingSystemName, ModificationTimestamp, OpenHouseKey, ListingId, OpenHouseDate` ## Query Parameters ### OData Query Support All resources support the following OData query parameters: - `$filter`: Filter results using OData filter expressions - `$select`: Select specific fields to return - `$top`: Limit the number of results (default: 500, max: 5000) - `$skip`: Skip a number of results for pagination Additionally, the Property resource supports: - `$expand`: Include related Media, OpenHouse, PropertyChange, PreferredMedia, PropertyRooms, and PropertyUnitTypes data Our API supports comprehensive OData filtering capabilities: #### Comparison Operators - `eq`: Equal to - `ne`: Not equal to - `gt`: Greater than - `ge`: Greater than or equal to - `lt`: Less than - `le`: Less than or equal to #### Logical Operators - `and`: Both conditions must be true - `or`: Either condition must be true - `not`: Negates the condition #### String Functions - `contains(field, 'value')`: Checks if field contains the specified value - `startswith(field, 'value')`: Checks if field starts with the specified value - `endswith(field, 'value')`: Checks if field ends with the specified value Example complex queries: ``` # Multiple conditions $filter=PropertyType eq 'Residential' and StandardStatus eq 'Active' # String function with logical operator $filter=startswith(OfficeMlsId, 'P') or contains(OfficeName, 'Real') # Date comparison $filter=ModificationTimestamp gt '2023-01-01T00:00:00Z' ``` ## API Grouping Results The OData API supports data aggregation using `$apply=groupby()`. Note that ALL grouping is case insensitive for string fields. ### Syntax ```http $apply=groupby((field1,field2,...)) ``` ### Response Schema The grouping response contains the following: - **group** - **groupby**: Field being grouped. - **count**: Total groups. - **buckets**: Array of group results. - **key**: Group value. - **count**: Items in the group. - **group**: Nested grouping (for multiple fields). ### Example Response ```json { "group": { "groupby": "OriginatingSystemName", "count": 18, "buckets": [ { "key": "global mls", "count": 89656, "group": { "groupby": "StateOrProvince", "count": 17, "buckets": [ { "key": "fl", "count": 87267 }, { "key": "", "count": 2307 } ] } } ] } } ``` ## Error Handling All errors follow the standard OData format: ```json { "error": { "code": "error_code", "message": "Human readable error message" } } ```