Package Exports
- @land-catalyst/batch-data-sdk
- @land-catalyst/batch-data-sdk/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@land-catalyst/batch-data-sdk) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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 Subscription with Event Hub
const subscription = new PropertySubscriptionBuilder()
.searchCriteria((c) => c.query("Phoenix, AZ").orQuickLists(["on-market"]))
.deliveryConfig((dc) =>
dc.eventHub({
fullyQualifiedNamespace: "mynamespace.servicebus.windows.net",
clientId: "12345678-1234-1234-1234-123456789abc",
tenantId: "87654321-4321-4321-4321-cba987654321",
clientSecret: "your-client-secret",
eventHubName: "property-events",
})
)
.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, Kinesis, or Event Hub delivery configurationEventHubConfig- Azure Event Hub 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" > .npmrc
Set your npm token as an environment variable:
export NPM_TOKEN=your-npm-token-here
Publish:
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 public
Enable 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