The BatchData Property Search API enables developers to integrate the means for searching and retrieving detailed information on real-estate properties. The API implements a very robust search grammar designed to support a wide-range of use cases.
For example, search criteria can be defined to:
Return a list of properties located in a particular geographic location. This is accomplished using a simple syntax where the location is specified using a single text string. For example, to search for properties in a city, simply set the search criteria query string to "Phoenix, AZ".
Return a list of properties that are comparable to a subject property. The comparables search grammar supports the ability apply search criteria that are relative to the subject property. For example, returning comparable properties whose living area square footage is within a certain percentage of the subject property, or within a certain radius of the subject property.
Return a list of properties that match certain characteristics of the property owner. For example, returning a list of properties where the total number of properties owned by the property owner is greater than a certain value.
Setting up the search criteria
The Property Search API accepts two primary JSON objects as input parameters, searchCriteria and options.
The searchCriteria object is used to specify what properties should be returned in the search results. The options object is used to further refine the API response and to control how many records to retrieve in each request.
searchCriteria has multiple properties to define the filtering criteria, out of which query is the most commonly used property. This property expects a US location such as a city, county, zip code, or State.
To help you understand better, let's look at some of the examples.
Retrieving a list of the properties in Phoenix, Arizona can be done by setting the query parameter to specify the location:
{
"searchCriteria": {
"query": "Phoenix, AZ"
}
}
Searching for properties in Phoenix whose last sale price was between $50,000 and $100,000 can be achieved by adding additional search conditions:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"sale": {
"lastSalePrice": {
"min": 50000,
"max": 100000
}
}
}
}
The search results can be further refined to return a list of properties whose last sale prices fall within the $50,000 - $100,000 range and with lot sizes ranging between 0.1 - 0.5 acres:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"sale": {
"lastSalePrice": {
"min": 50000,
"max": 100000
}
},
"lot": {
"lotSizeAcres": {
"min": 0.1,
"max": 0.5
}
}
}
}
Note: All search conditions are AND'd together and have the net effect of further reducing the number of properties that are returned in the search results.
Search criteria with numeric values
The two examples just shown used additional search criteria that accept numeric values. The search grammar for numeric search attributes allows for a minimum and a maximum value to be specified. The min and max operators can be combined to specify a range of values, or they can be used on their own. For example, to match properties that have a lot size of at least 2 acres, just specify the min value:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"sale": {
"lastSalePrice": {
"min": 50000,
"max": 100000
}
},
"lot": {
"lotSizeAcres": {
"min": 2
}
}
}
}
The following search criteria matches properties whose last sale price did not exceed $100k. No minimum value is specified. This search is more concisely expressed as "Properties in Phoenix, AZ with a last sale price <= $100k and a minimum lot size of 2 acres":
{
"searchCriteria": {
"query": "Phoenix, AZ",
"sale": {
"lastSalePrice": {
"max": 100000
}
},
"lot": {
"lotSizeAcres": {
"min": 2
}
}
}
}
Search criteria with date values
Date based search criteria have a similar search grammar to numeric values. The minDate and maxDate operators can be used in combination or by themselves.
Here is an example of a search for properties with auction dates that are scheduled to occur in the next 7 days. The example assumes that the current date is December 8, 2024:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"foreclosure": {
"auctionDate": {
"minDate": "2024-12-09",
"maxDate": "2024-12-15"
}
}
}
}
This example returns properties that were sold in the past 12 months assuming the current date is December 8, 2024. Note that we are using the intel.lastSold attribute. intel.lastSold is a composite attribute that considers both the sale.lastSaleDate and listing.soldDate when applying the search criteria:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"intel": {
"lastSoldDate": {
"minDate": "2023-12-08"
}
}
}
}
Search criteria with text values
Now let's introduce some search conditions that make use of text values. The BatchData search grammar supports a robust set of operators for applying text based search conditions.
Consider the following search for residential properties:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"general": {
"propertyTypeCategory": {
"equals": "Residential"
}
}
}
}
The equals operator is used to match properties where general.propertyTypeCategory = "Residential".
We can further refine our search results to only return certain types of residential properties by making use of the inList operator to specify a list of values that the propertyTypeDetail attribute must match:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"general": {
"propertyTypeCategory": {
"equals": "Residential"
},
"propertyTypeDetail": {
"inList": [
"Single Family",
"Multi-Family"
]
}
}
}
}
The inList operator is effectively OR'ing the values in the array. The previous search is more concisely expressed as properties where the propertyTypeCategory = "Residential" AND the propertyTypeDetail is "Single Family" OR "Multi-Family".
The complete list of values that can be applied to general.propertyTypeCategory and general.propertyTypeDetail criteria are documented in the "Property Search/Property Lookup API codes and descriptions" data dictionary.
Advanced location searches
While the query property provides the easiest way to search by location, it is limited to searching a single location. We can take advantage of the robust text search grammar to create more advanced location searches.
Searching multiple locations
A common use case is to identify properties located in a specific list of cities, counties, or zip codes. This can be accomplished by using the address property in conjunction with the inList operator.
For example, let's say you want to match properties in 3 cities. We omit the query property and instead make use of the address.cityState property:
{
"searchCriteria": {
"address": {
"cityState": {
"inList": [
"Phoenix, AZ",
"Tempe, AZ",
"Chandler, AZ"
]
}
}
}
}
The same search results can be accomplished using address.city and address.state:
{
"searchCriteria": {
"address": {
"city": {
"inList": [
"Phoenix",
"Tempe",
"Chandler"
]
},
"state": {
"equals": "AZ"
}
}
}
}
Let's say the desired outcome is to search for properties in a couple of counties while excluding properties in certain zip codes. Here we can make use of the notInList operator:
{
"searchCriteria": {
"address": {
"county": {
"inList": [
"Maricopa",
"Pima"
]
},
"state": {
"equals": "AZ"
},
"zip": {
"notInList": [
"85337",
"85390"
]
}
}
}
}
To search a set of neighborhoods, list their complete names with the inList operator on the address.locality property. This example finds properties in three Phoenix urban villages:
{
"searchCriteria": {
"address": {
"county": {
"inList": [
"Maricopa",
"Pima"
]
},
"state": {
"equals": "AZ"
},
"locality": {
"inList": [
"Laveen Village",
"South Mountain Village",
"Ahwatukee Foothills Village"
]
}
}
}
}
Each value in inList must be a complete stored name (a fragment inside inList matches nothing). The wildcard operators (contains, startsWith, endsWith) are not supported on address.locality.
Property Search supports eight text operators:
equals- search for an exact matchcontains- search for results that contain the value. Not supported on the high and medium cardinality text fields (see below).startsWith- search for results that start with the value. Not supported on the high and medium cardinality text fields (see below).endsWith- search for results that end with the value. Not supported on the high and medium cardinality text fields (see below).inList- search for results that match any of the strings in the arraynotInList- search for results that match none of the strings in the arraymatches- search for results that match a complete stored value. Usematcheswith a single value, where it behaves likeequals, and useinListto match any of several values.containsWords- tokenized, order-insensitive, case-insensitive word match on current-owner names, available onowner.firstNameandowner.lastNameonly.
The wildcard operators (contains, startsWith, endsWith) are not supported on the high and medium cardinality Property Search text fields, for example address.street, address.city, address.county, address.locality, owner.firstName, owner.lastName, and owner.fullName. If you send a wildcard operator on a restricted field, the API returns an HTTP 400 naming the field and operator. The wildcard operators remain available on the low-cardinality enumerated fields, for example address.state, address.zip, general.propertyTypeCategory, and general.propertyTypeDetail. For the full list of restricted fields and the operators to use on them, see Text Search Grammar Best Practices: Choose the Right Operator.
Array Size Limit: Arrays passed to inList and notInList should not exceed 10,000 values. Requests with arrays larger than 10,000 values may silently return incorrect results. The API will not return an error, but matching behavior becomes unreliable. Always monitor your result counts when working with large exclusion lists to verify the filter is being applied correctly.
Quick Lists
As you can see, the search grammar implemented by the Property Search API provides a lot of flexibility and can be used to define very complex search queries. However BatchData goes one step further by making it easy to implement search conditions for the most common real-estate investor use cases by predefining a large set of search queries. These predefined search queries are called Quick Lists and are accessed using the quickList, quickLists, and orQuickLists search criteria.
For example, a common use case for real-estate investors is to identify properties that are owned by individuals that live outside of the state where the property is located. We can easily implement that search by adding just one additional condition using the out-of-state-absentee-owner quick list:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"quickList": "out-of-state-absentee-owner"
}
}
Multiple quick lists can be combined using either the quickLists search criteria which AND's the lists, or the orQuickLists search criteria which OR's the lists.
For example, to return properties where the owner is both out of state and is in default of their loan:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"quickLists": [
"out-of-state-absentee-owner",
"notice-of-default"
]
}
}
Use the orQuickLists search criteria to return properties where the owner is out of state or is in default of their loan:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"orQuickLists": [
"out-of-state-absentee-owner",
"notice-of-default"
]
}
}
The not- operator can be applied to quick lists to negate them. For example, to search for properties where the owner is not an out of state owner but is in default of their loan we simply prefix the quick list with "not-":
{
"searchCriteria": {
"query": "Phoenix, AZ",
"quickLists": [
"not-out-of-state-absentee-owner",
"notice-of-default"
]
}
}
The complete list of quick lists and their descriptions is documented in Property Search/Property Lookup API datasets.
Structuring the result
If certain sections of the API response, such as building, listing, sale, or valuation data, are missing, it may indicate that your API token lacks the necessary datasets. Ensure your token is provisioned correctly to include all required datasets.
The options object enables the customization of search results. It is optional and contains various properties with useful customization functions.
Here are some of the important options:
skip - denotes the number of records to skip from the results. Used when retrieving paginated results, typically in combination with take. Default value is 0.
take - denotes the number of property results to return per response. Default value is 25. The maximum value is 100 on the synchronous endpoint POST /api/v1/property/search and 1000 on the asynchronous endpoint POST /api/v1/property/search/async. Passing take: 0 returns aggregate data only (counts, summaries) and is billed for one property record at the request’s resolved dataset rate (the basic property dataset rate if no dataset resolves). take values greater than 0 are billed only for the records actually returned, with no minimum charge, and a request that returns no matching properties is not charged.
Pagination type: The Property Search API supports two pagination styles: offset-based pagination via skip and take, and cursor-based pagination via options.useCursorPagination: true with pageCursor. Offset pagination is fine for small result sets; cursor-based pagination is recommended for paging reliably through large result sets, and Search Sessions can be layered on top for incremental delivery without re-billing properties already delivered. See Incremental Property Search: Cursor Pagination & Search Sessions for details.
Total count: Every response includes a resultsFound field in the results.meta.results object. This value represents the total number of properties that match your search criteria, regardless of pagination settings. Use resultsFound to determine how many pages of results are available.
Paginating through results
A Property Search request can potentially return thousands of matched properties. Use skip and take to retrieve results in pages.
First request — retrieve the first 100 properties and check the total count:
{
"searchCriteria": {
"query": "Phoenix, AZ"
},
"options": {
"skip": 0,
"take": 100
}
}
The response will include results.meta.results.resultsFound (e.g., 5,432). Use this value to determine how many subsequent requests are needed.
Second request — retrieve the next 100 properties by advancing skip:
{
"searchCriteria": {
"query": "Phoenix, AZ"
},
"options": {
"skip": 100,
"take": 100
}
}
Continue incrementing skip by the take value until you have retrieved all results or reached your desired count.
Billing note: Each property returned in a response counts toward your usage, whether you are on a PAYG or subscription plan. The number of properties returned is in results.meta.results.resultCount. A take: 0 (count-only) request is billed for one property record at the resolved dataset rate; a request with take greater than 0 that returns no matching properties is not charged.
Avoiding duplicates with offset drifting
When paginating through large result sets using skip and take, the underlying dataset may change between requests (new records added, existing records updated). This can occasionally cause duplicate or missing properties across pages — a behavior known as offset drifting. For most use cases, simple skip/take pagination is sufficient and offset drifting has minimal impact.
However, if your application requires that no duplicate properties are returned across paginated requests — for example, when building a unique lead list or performing incremental data pulls — you can use the ids.propertyId.notInList filter to exclude previously retrieved property IDs from subsequent requests:
{
"searchCriteria": {
"query": "Phoenix, AZ",
"ids": {
"propertyId": {
"notInList": [
"prop-id-1",
"prop-id-2",
"prop-id-3"
]
}
}
},
"options": {
"skip": 0,
"take": 100
}
}
When using this approach, set skip back to 0 on each subsequent request since the exclusion list itself advances your position in the result set.
You can also use ids.addressHash.notInList to exclude properties by their address hash instead of property ID. Both approaches are valid — use whichever identifier your application stores.
Keep exclusion lists under the 10,000-value limit described in the text operators section above.
quicklistCounts - indicates whether or not you want to see quicklist counts in the response.
dateFormat - a flag indicating whether the date format should be datetime or date. Possible values are "iso-date-time" and "iso-date". When not specified it defaults to "iso-date-time".
{
"searchCriteria": {
"query": "Phoenix, AZ"
},
"options": {
"quicklistCounts": true,
"dateFormat": "iso-date"
}
}
sort - use to order the response by a particular field. Takes two inputs: sortOrder and field. sortOrder defines whether you want results in ascending, descending, or random order (denoted as "asc", "desc", or "random"). field denotes the column by which to order the response.
Supported sort fields: bathroomCount, bedroomCount, city, daysOnMarket, equityCurrentEstimatedBalance, estimatedValue, foreclosureStatus, lastSalePrice, lastSaleDate, lastSoldDate, lastSoldPrice, livingAreaSquareFeet, mlsStatus, ownerOccupied, price, propertyType, propertyTypeCategory, soldDate, state, totalBuildingAreaSquareFeet, yearBuilt, zip.
{
"searchCriteria": {
"query": "Phoenix, AZ"
},
"options": {
"sort": {
"sortOrder": "asc",
"field": "city"
}
}
}
Billing and Dataset Costs
Understanding the billing model for Property Search and Property Lookup endpoints is crucial for cost-effective API usage and integration planning.
Token-Based Billing: Property Search and Property Lookup API costs are determined by your authentication token's provisioned dataset access, not by the specific data requested in individual API calls.
Why This Billing Model?: This token-based per-record billing approach provides predictable cost estimation and transparent usage reporting. You can accurately forecast API expenses based on expected property record volumes and your token's dataset provisioning. Usage reports clearly correlate charges to property records retrieved, making it simple to validate billing against actual usage and understand cost breakdowns.
Dataset Provisioning: Each API token is configured during setup with access to specific datasets. This provisioning determines your per-record billing rate for Property Search and Property Lookup requests.
Per-Record Costs: Every property record returned from Property Search and Property Lookup endpoints costs the full per-record rate for all datasets your token is provisioned to access. For example:
Token provisioned for multiple datasets: Each property costs the combined rate for all provisioned datasets
Runtime request for subset of data: Still charged the full rate for all provisioned datasets
Runtime Parameters Don't Affect Billing: The datasets and customProjection parameters in Property Search and Property Lookup requests filter which data is returned but do not reduce costs:
datasetsparameter filters which data categories are returned but you're charged for all provisioned datasetscustomProjectionspecifies which fields to return but doesn't change per-record costs
Cost Optimization Strategies:
Provision tokens with only datasets needed for your specific use case
Use
takeparameter to limit the number of records returned per searchImplement appropriate search criteria to get relevant results without over-fetching
Use sandbox tokens for development and testing to avoid charges
Billing Transparency: Your account dashboard shows detailed usage and costs broken down by dataset and time period for complete visibility into API expenses.
Production Planning: Before deploying to production, work with your account manager to optimize token provisioning based on your application's actual data needs.
For comprehensive dataset information and pricing details, see Datasets and Custom Projections.
More information on all the available properties within searchCriteria and options can be found in the Property Search API Reference.
