@land-catalyst/batch-data-sdk
v1.1.12
Published
TypeScript SDK for BatchData.io Property API - Types, Builders, and Utilities
Maintainers
Readme
@land-catalyst/batch-data-sdk
TypeScript SDK for BatchData.io Property API - Complete type definitions, fluent query builders, and utilities.
Installation
This package is published to the public npm registry. To install it:
npm install @land-catalyst/batch-data-sdkOr add to package.json:
{
"dependencies": {
"@land-catalyst/batch-data-sdk": "^1.0.0"
}
}Features
- ✅ Complete TypeScript Types - Full type definitions for all BatchData API endpoints and responses
- ✅ Fluent Query Builders - Type-safe, chainable API for constructing search criteria
- ✅ Property Search Response Types - Comprehensive types for property search results
- ✅ HTTP Client - Ready-to-use API client for BatchData endpoints
- ✅ Custom Error Classes - Specialized error handling for BatchData operations
Examples
Client Setup
import { BatchDataClient } from "@land-catalyst/batch-data-sdk";
import { ConsoleLogger } from "@land-catalyst/batch-data-sdk";
// Create a client instance
const client = new BatchDataClient({
apiKey: process.env.BATCHDATA_API_KEY!,
// Optional: provide custom logger
logger: new ConsoleLogger(),
});Basic Property Search
import {
SearchCriteriaBuilder,
PropertySearchRequestBuilder,
PropertyLookupOptionsBuilder,
} from "@land-catalyst/batch-data-sdk";
// Simple search by location
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) => c.query("Maricopa County, AZ"))
.options((o) => o.take(10).skip(0))
.build();
const response = await client.searchProperties(request);Address-Based Search
import {
SearchCriteriaBuilder,
PropertySearchRequestBuilder,
} from "@land-catalyst/batch-data-sdk";
// Search by address with filters
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.address((a) =>
a
.city((city) => city.equals("Phoenix"))
.state((state) => state.equals("AZ"))
.zip((zip) => zip.inList(["85001", "85002", "85003"]))
.street((street) => street.contains("Main"))
)
)
.build();
const response = await client.searchProperties(request);String Filter Examples
import { StringFilterBuilder } from "@land-catalyst/batch-data-sdk";
// All string filter methods can be chained
const filter = new StringFilterBuilder()
.equals("Phoenix")
.contains("Phx")
.startsWith("Ph")
.endsWith("ix")
.matches(["Phoenix.*", ".*Arizona"])
.inList(["Phoenix", "Tucson", "Flagstaff"])
.build();
// Use in address search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("AZ")
.address((a) =>
a
.city((city) => city.equals("Phoenix").contains("Phx"))
.state((state) => state.inList(["AZ", "CA", "NV"]))
)
)
.build();Numeric Range Filter Examples
import { NumericRangeFilterBuilder } from "@land-catalyst/batch-data-sdk";
// Numeric ranges with min/max
const filter = new NumericRangeFilterBuilder()
.min(100000)
.max(500000)
.build();
// Use in assessment search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.assessment((a) =>
a
.totalAssessedValue((v) => v.min(100000).max(500000))
.assessmentYear((y) => y.min(2020).max(2023))
)
)
.build();Date Range Filter Examples
import { DateRangeFilterBuilder } from "@land-catalyst/batch-data-sdk";
// Date ranges
const filter = new DateRangeFilterBuilder()
.minDate("2020-01-01")
.maxDate("2023-12-31")
.build();
// Use in sale search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("AZ")
.sale((s) =>
s.lastSaleDate((d) =>
d.minDate("2020-01-01").maxDate("2023-12-31")
)
)
)
.build();Building Criteria Search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.building((b) =>
b
.yearBuilt((y) => y.min(1990).max(2020))
.bedroomCount((br) => br.min(3).max(5))
.bathroomCount((ba) => ba.min(2).max(4))
.totalBuildingAreaSquareFeet((area) => area.min(1500).max(3000))
.buildingType((type) => type.equals("Single Family"))
.pool((pool) => pool.equals("Yes"))
.airConditioningSource((ac) => ac.contains("Central"))
)
)
.build();Assessment Criteria Search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.assessment((a) =>
a
.totalAssessedValue((v) => v.min(100000).max(500000))
.totalMarketValue((v) => v.min(200000).max(600000))
.assessmentYear((y) => y.min(2020).max(2023))
)
)
.build();Demographics Criteria Search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.demographics((d) =>
d
.age((a) => a.min(25).max(65))
.income((i) => i.min(50000).max(150000))
.netWorth((nw) => nw.min(100000).max(1000000))
.householdSize((hs) => hs.min(2).max(5))
.homeownerRenter((hr) => hr.equals("Homeowner"))
.gender((g) => g.equals("M").inList(["M", "F"]))
.hasChildren(true)
.businessOwner((bo) => bo.equals("Yes"))
)
)
.build();Complex Multi-Criteria Search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
// Address filters
.address((a) =>
a
.city((city) => city.equals("Phoenix"))
.state((state) => state.equals("AZ"))
.zip((zip) => zip.startsWith("85"))
)
// Building filters
.building((b) =>
b
.yearBuilt((y) => y.min(1990).max(2020))
.bedroomCount((br) => br.min(3).max(5))
.bathroomCount((ba) => ba.min(2).max(4))
)
// Assessment filters
.assessment((a) =>
a
.totalAssessedValue((v) => v.min(100000).max(500000))
.assessmentYear((y) => y.min(2020).max(2023))
)
// Demographics filters
.demographics((d) =>
d
.income((i) => i.min(50000).max(150000))
.age((a) => a.min(25).max(65))
)
// Quick list
.quickList("vacant")
)
.options((o) =>
o
.take(50)
.skip(0)
.bedrooms(3, 5)
.bathrooms(2, 4)
.yearBuilt(1990, 2020)
.skipTrace(true)
.images(true)
.quicklistCounts(true)
)
.build();Property Search with Options
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) => c.query("Maricopa County, AZ"))
.options((o) =>
o
// Pagination
.pagination(0, 50)
.skip(0)
.take(50)
// Distance filtering
.distance(5) // 5 miles
.distance(undefined, 8800, 26400) // yards and feet
.distance(undefined, undefined, undefined, 8, 8047) // km and meters
// Bounding box
.boundingBox(
{ latitude: 33.5, longitude: -112.1 },
{ latitude: 33.4, longitude: -112.0 }
)
// Property features
.bedrooms(3, 5)
.bathrooms(2, 4)
.stories(1, 2)
.area(80, 120) // percentage
.yearBuilt(1990, 2020)
.lotSize(90, 110) // percentage
// Flags
.skipTrace(true)
.aggregateLoanTypes(true)
.images(true)
.showRequests(true)
.areaPolygon(true)
.quicklistCounts(true)
.useSubdivision(true)
// Formatting
.dateFormat("YYYY-MM-DD")
// Sorting
.sort("totalAssessedValue", "desc", "session-12345")
.build()
)
.build();Property Subscription
import {
PropertySubscriptionBuilder,
DeliveryConfigBuilder,
} from "@land-catalyst/batch-data-sdk";
// Webhook subscription
const subscription = new PropertySubscriptionBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.address((a) =>
a
.city((city) => city.equals("Phoenix"))
.state((state) => state.equals("AZ"))
)
.quickList("vacant")
)
.deliveryConfig((dc) =>
dc.webhook("https://example.com/webhook", {
"X-API-Key": "secret",
})
)
.build();
const response = await client.createPropertySubscription(subscription);Property Subscription with Kinesis
const subscription = new PropertySubscriptionBuilder()
.searchCriteria((c) => c.query("US").quickList("owner-occupied"))
.deliveryConfig((dc) =>
dc.kinesis(
"my-stream",
"us-east-1",
"access-key-id",
"secret-access-key"
)
)
.build();Property Lookup by Address
import {
PropertyLookupRequestBuilder,
PropertyLookupRequestItemBuilder,
} from "@land-catalyst/batch-data-sdk";
// Single property lookup
const request = new PropertyLookupRequestBuilder()
.addItem(
new PropertyLookupRequestItemBuilder()
.address({
street: "2800 N 24th St",
city: "Phoenix",
state: "AZ",
zip: "85008",
})
.build()
)
.options((o) => o.take(1).skipTrace(true))
.build();
const response = await client.lookupProperty(request);Property Lookup by Multiple Identifiers
const request = new PropertyLookupRequestBuilder()
.addItem(
new PropertyLookupRequestItemBuilder()
.propertyId("12345")
.build()
)
.addItem(
new PropertyLookupRequestItemBuilder()
.apn("123-45-678")
.countyFipsCode("04013")
.build()
)
.addItem(
new PropertyLookupRequestItemBuilder()
.hash("abc123def456")
.build()
)
.options((o) =>
o
.take(10)
.images(true)
.skipTrace(true)
.showRequests(true)
)
.build();Async Property Lookup
import {
PropertyLookupAsyncRequestBuilder,
AsyncPropertyLookupOptionsBuilder,
} from "@land-catalyst/batch-data-sdk";
const request = new PropertyLookupAsyncRequestBuilder()
.addItem(
new PropertyLookupRequestItemBuilder()
.address({
street: "2800 N 24th St",
city: "Phoenix",
state: "AZ",
zip: "85008",
})
.build()
)
.options(
new AsyncPropertyLookupOptionsBuilder()
.webhook(
"https://example.com/webhook",
"https://example.com/error-webhook"
)
.build()
)
.build();
const response = await client.lookupPropertyAsync(request);Async Property Search
import { PropertySearchAsyncRequestBuilder } from "@land-catalyst/batch-data-sdk";
const request = new PropertySearchAsyncRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
.address((a) =>
a
.city((city) => city.equals("Phoenix"))
.state((state) => state.equals("AZ"))
)
)
.options(
new AsyncPropertyLookupOptionsBuilder()
.webhook("https://example.com/webhook")
.take(100)
.build()
)
.build();
const response = await client.searchPropertiesAsync(request);Address Verification
const request = {
requests: [
{
street: "2800 N 24th St",
city: "Phoenix",
state: "Arizona",
zip: "85008",
},
],
};
const response = await client.verifyAddress(request);Address Autocomplete
const request = {
searchCriteria: {
query: "2800 N 24th St Phoenix",
},
options: {
skip: 0,
take: 5,
},
};
const response = await client.autocompleteAddress(request);Geocoding
// Geocode address to coordinates
const geocodeRequest = {
requests: [
{
address: "2800 N 24th St, Phoenix, AZ, 85008",
},
],
};
const geocodeResponse = await client.geocodeAddress(geocodeRequest);
// Reverse geocode coordinates to address
const reverseGeocodeRequest = {
request: {
latitude: 33.47865,
longitude: -112.03029,
},
};
const reverseGeocodeResponse =
await client.reverseGeocodeAddress(reverseGeocodeRequest);Builder Accumulation Pattern
// Multiple calls to the same method accumulate changes
const criteria = new AddressSearchCriteriaBuilder()
.street((s) => s.equals("Main"))
.street((s) => s.contains("St"))
.street((s) => s.startsWith("M"))
.build();
// Result: { street: { equals: "Main", contains: "St", startsWith: "M" } }
// Direct values replace previous builder configuration
const criteria2 = new AddressSearchCriteriaBuilder()
.street((s) => s.equals("Main St"))
.street({ equals: "New Street" }) // Replaces previous
.build();
// Result: { street: { equals: "New Street" } }Using Direct Values vs Lambda Configurators
// Both patterns are supported - use whichever is more convenient
// Lambda pattern (recommended for complex configurations)
const request1 = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("AZ")
.address((a) =>
a
.city((city) => city.equals("Phoenix").contains("Phx"))
.state((state) => state.inList(["AZ", "CA"]))
)
)
.build();
// Direct value pattern (useful for simple cases)
const request2 = new PropertySearchRequestBuilder()
.searchCriteria({
query: "AZ",
address: {
city: { equals: "Phoenix" },
state: { equals: "AZ" },
},
})
.options({
take: 10,
skip: 0,
})
.build();Complex Nested Search
// Ultra-complex search with all criteria types
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Maricopa County, AZ")
// Address with multiple filters
.address((a) =>
a
.street((s) =>
s
.equals("Main")
.contains("Street")
.startsWith("M")
.endsWith("St")
.inList(["Main St", "Main Street", "Main Ave"])
)
.city((city) =>
city.equals("Phoenix").contains("Phx").matches(["Phoenix.*"])
)
.state((state) => state.equals("AZ"))
.zip((zip) => zip.startsWith("85").endsWith("01"))
)
// Assessment with accumulation
.assessment((a) =>
a
.assessmentYear((y) => y.min(2020).max(2023))
.assessmentYear((y) => y.min(2019)) // Accumulates
.totalAssessedValue((v) => v.min(100000).max(500000))
.totalAssessedValue((v) => v.min(150000)) // Accumulates
)
// Building criteria
.building((b) =>
b
.buildingType((t) =>
t.equals("Single Family").inList([
"Single Family",
"Townhouse",
"Condo",
])
)
.yearBuilt((y) => y.min(1990).max(2020))
.bedroomCount((br) => br.min(3).max(5))
.bathroomCount((ba) => ba.min(2).max(4))
.pool((pool) => pool.equals("Yes"))
)
// Demographics
.demographics((d) =>
d
.age((a) => a.min(25).max(65))
.income((i) => i.min(50000).max(150000))
.income((i) => i.min(60000)) // Accumulates
.gender((g) => g.equals("M").inList(["M", "F"]))
.hasChildren(true)
)
// Quick lists
.quickList("vacant")
)
.options((o) =>
o
.pagination(100, 50)
.distance(5, 8800, 26400, 8, 8047)
.bedrooms(3, 5)
.bathrooms(2, 4)
.yearBuilt(1990, 2020)
.skipTrace(true)
.images(true)
.sort("totalAssessedValue", "desc", "session-12345")
.build()
)
.build();Error Handling
import { PropertyCountLimitExceededError } from "@land-catalyst/batch-data-sdk";
try {
const response = await client.searchProperties(request);
} catch (error) {
if (error instanceof PropertyCountLimitExceededError) {
console.log(`Limit exceeded: ${error.count} > ${error.limit}`);
console.log(`Limit type: ${error.limitType}`); // "sync" or "async"
} else if (error instanceof Error) {
console.error("API error:", error.message);
}
}Using Builders with Existing Data
// Create builder from existing criteria
const existingCriteria: SearchCriteria = {
query: "AZ",
address: {
city: { equals: "Phoenix" },
},
};
const builder = SearchCriteriaBuilder.from(existingCriteria);
builder.address((a) => a.state((s) => s.equals("AZ")));
const updatedCriteria = builder.build();GeoLocation Filters
import { GeoLocationFactory } from "@land-catalyst/batch-data-sdk";
// Distance-based search
const request = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Phoenix, AZ")
.address((a) =>
a.geoLocationDistance(33.4484, -112.074, {
miles: "5",
})
)
)
.build();
// Bounding box search
const request2 = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Phoenix, AZ")
.address((a) =>
a.geoLocationBoundingBox(
33.5, // NW latitude
-112.1, // NW longitude
33.4, // SE latitude
-112.0 // SE longitude
)
)
)
.build();
// Polygon search
const request3 = new PropertySearchRequestBuilder()
.searchCriteria((c) =>
c
.query("Phoenix, AZ")
.address((a) =>
a.geoLocationPolygon([
{ latitude: 33.5, longitude: -112.1 },
{ latitude: 33.4, longitude: -112.1 },
{ latitude: 33.4, longitude: -112.0 },
{ latitude: 33.5, longitude: -112.0 },
])
)
)
.build();All Search Criteria Types
// Foreclosure search
.searchCriteria((c) =>
c
.query("AZ")
.foreclosure((f) =>
f
.status((s) => s.equals("Active"))
.recordingDate((d) => d.minDate("2020-01-01"))
)
)
// Listing search
.listing((l) =>
l
.price((p) => p.min(200000).max(500000))
.status((s) => s.equals("Active"))
.daysOnMarket((d) => d.min(0).max(90))
)
// Sale search
.sale((s) =>
s
.lastSalePrice((p) => p.min(100000).max(400000))
.lastSaleDate((d) => d.minDate("2020-01-01"))
)
// Owner search
.owner((o) =>
o
.firstName((f) => f.equals("John"))
.lastName((l) => l.equals("Doe"))
.ownerOccupied(true)
)
// Open lien search
.openLien((ol) =>
ol
.totalOpenLienCount((c) => c.min(1).max(5))
.totalOpenLienBalance((b) => b.min(50000).max(500000))
)
// Permit search
.permit((p) =>
p
.permitCount((c) => c.min(1).max(10))
.totalJobValue((v) => v.min(10000).max(100000))
)
// Tax search
.tax((t) => t.taxDelinquentYear((y) => y.min(2020).max(2023)))
// Valuation search
.valuation((v) =>
v
.estimatedValue((ev) => ev.min(200000).max(600000))
.ltv((ltv) => ltv.min(0).max(80))
)
// Intel search
.intel((i) =>
i
.lastSoldPrice((p) => p.min(150000).max(450000))
.lastSoldDate((d) => d.minDate("2020-01-01"))
.salePropensity((sp) => sp.min(50).max(100))
)Quick Lists
// Single quick list
.quickList("vacant")
// Multiple quick lists (AND)
.quickLists(["vacant", "owner-occupied", "high-equity"])
// Multiple quick lists (OR)
.orQuickLists(["vacant", "preforeclosure"])
// Exclude quick list
.quickList("not-vacant")Sorting and Pagination
.options((o) =>
o
// Pagination
.pagination(0, 50) // skip 0, take 50
.skip(100) // Override skip
.take(25) // Override take
// Sorting
.sort("totalAssessedValue", "desc")
.sort("yearBuilt", "asc", "session-12345") // With session ID
.build()
)Response Handling
const response = await client.searchProperties(request);
// Check status
if (response.status?.code === 200) {
console.log("Success!");
}
// Access properties
if (response.results?.properties) {
response.results.properties.forEach((property) => {
console.log(property.address?.street);
console.log(property.valuation?.estimatedValue);
console.log(property.demographics?.income);
console.log(property.building?.yearBuilt);
});
}
// Access metadata
if (response.results?.meta) {
console.log(
`Found ${response.results.meta.results?.resultsFound} properties`
);
console.log(
`Request took ${response.results.meta.performance?.totalRequestTime}ms`
);
}
// Quick list counts
if (response.results?.quicklistCounts) {
response.results.quicklistCounts.forEach((count) => {
console.log(`${count.name}: ${count.count}`);
});
}API Documentation
Types
SearchCriteria- Complete search criteria structurePropertySubscriptionRequest- Subscription creation requestPropertySubscriptionResponse- Subscription creation responsePropertySearchResponse- Property search API responseProperty- Individual property object with all nested dataDeliveryConfig- Webhook or Kinesis delivery configuration
Builders
All builders follow a fluent, chainable pattern:
SearchCriteriaBuilder- Main search criteria builderAddressSearchCriteriaBuilder- Address filtersBuildingSearchCriteriaBuilder- Building/property filtersStringFilterBuilder- String comparison filtersNumericRangeFilterBuilder- Numeric range filtersDateRangeFilterBuilder- Date range filters- And many more...
Errors
PropertyCountLimitExceededError- Thrown when search results exceed configured limits
Development
# Install dependencies
npm install
# Build the package
npm run build
# Run tests
npm test
# Run integration tests (requires BATCHDATA_API_KEY)
npm run test:integration
# The built files will be in ./distRunning Integration Tests
Integration tests make real API calls and require a valid API key:
export BATCHDATA_API_KEY=your_api_key_here
npm run test:integrationPublishing
This package is published to npm using trusted publishing (OIDC) via GitHub Actions. Publishing happens automatically when a version tag is pushed.
Manual Publishing (if needed)
If you need to publish manually, set up .npmrc to use your npm token:
Create
.npmrcfile (add to.gitignore- already configured):# Option 1: Use environment variable (recommended) echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc # Option 2: Use token directly (less secure) echo "//registry.npmjs.org/:_authToken=your-token-here" > .npmrcSet your npm token as an environment variable:
export NPM_TOKEN=your-npm-token-herePublish:
npm run build npm publish --access public
Note: The .npmrc file is already in .gitignore so it won't be committed. See .npmrc.example for a template.
Setting Up GitHub Actions Publishing (Trusted Publishing)
The workflow uses npm trusted publishing (OIDC) - no tokens needed! To set it up:
Initial Manual Publish (one-time, to create the package on npm):
# Set up your .npmrc first (see Manual Publishing section above) npm run build npm publish --access publicEnable Trusted Publishing on npm:
- Go to npmjs.com and log in
- Navigate to your package:
@land-catalyst/batch-data-sdk - Go to Package Settings → Automation → Trusted Publishing
- Click Add GitHub Actions workflow
- Select repository:
land-catalyst/batch-data-sdk - Select workflow file:
.github/workflows/cd.yml - Click Approve
Verify Setup:
- After enabling, the workflow will automatically publish to npm when you push a version tag
- No authentication tokens needed - OIDC handles it automatically!
- The package will show a verified checkmark on npm
For detailed setup instructions, see SETUP_NPM_PUBLISHING.md.
Versioning
To create a new version:
# Patch version (1.1.12 -> 1.1.13)
npm run v:patch
# Minor version (1.1.12 -> 1.2.0)
npm run v:minor
# Major version (1.1.12 -> 2.0.0)
npm run v:majorThis will:
- Update the version in
package.json - Create a git tag
- Push the tag to GitHub
- Trigger the CD workflow to publish to npm
License
MIT
