Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI
on:
pull_request:
push:
branches: [main, dev]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install
- run: npm test
- run: npx zapier validate
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 BatchData
Copyright (c) 2026 BatchData (batchdata.io)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
100 changes: 80 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# BatchData for Zapier

Zapier integration for the [BatchData](https://batchdata.io) API: search properties, look up a property record, skip trace owners, verify addresses, and check phone numbers for reachability, Do Not Call (DNC) and TCPA litigator status.
Zapier integration for the [BatchData](https://batchdata.io) API. It covers property search and lookup, skip tracing (V1 and V3), reverse skip trace, address tools, phone checks (reachability, DNC, TCPA), permits, investor buy box, search sessions and property monitors, with polling and instant (REST hook) triggers and a generic API request action.

Support: support@batchservice.com

> **Status: v0.1, not yet published.** The code passes its local tests with mocked HTTP. It has not been pushed to Zapier or run against the live BatchData API. See [Publishing](#publishing) and [What was and was not verified](#what-was-and-was-not-verified).
> **Status: v0.2.0, not yet pushed to Zapier.** It passes `zapier-platform validate` and its local tests. Most operations were smoke-tested live; monitors and the instant trigger were not (see [What was and was not verified](#what-was-and-was-not-verified)).

![BatchData](logo.png)

## Authentication

Expand Down Expand Up @@ -40,21 +42,58 @@ Searches return one record per person. The Action returns one object with `perso

Phone results are a point-in-time answer. Re-check before each campaign. You are responsible for meeting TCPA, DNC and other calling rules in your jurisdiction.

## Not included in v0.1
## Added in v0.2

Searches (return records to later steps) and actions (change something or send a batch):

| Zapier module | Type | Endpoint | Billing |
| --- | --- | --- | --- |
| Reverse Skip Trace (Phone or Email) | Search, Action | `POST /api/v3/property/reverse-skip-trace` | Per matched result. Flagged-phone setting always sent explicitly (default off). |
| Get Property Permits | Search | `POST /api/v2/property/get-property-permits` | Per the plan; see the module description. |
| Batch Skip Trace Properties (V3) | Action | `POST /api/v3/property/skip-trace` | Per matched result, up to 100 rows. |
| Batch Look Up Properties | Action | `POST /api/v1/property/lookup/all-attributes` | Per returned record, up to 100 rows. |
| Batch Verify Addresses | Action | `POST /api/v1/address/verify` | Per submitted address, up to 100 rows. |
| Autocomplete, Geocode, Reverse Geocode | Search | `POST /api/v1/address/...` | This call does not bill property records. |
| Find Investors (Buy Box), Get Investor Properties | Search | `POST /api/v2/investor/...` | See the module description. |
| List / Create or Advance / Delete Search Session | Search, Action | `/api/v2/property/search-sessions`, `POST /api/v1/property/search` | Creating or advancing a session bills each record delivered. Sessions are capped per team and need the search-sessions token ability. |
| Get / Create / Delete Property Monitor | Search, Action | `/api/v2/property-subscription` | Billed according to your plan (support@batchservice.com). |
| API Request (Advanced) | Action | any `/api/...` path | Depends on the endpoint called. |

Triggers:

| Trigger | Type | Notes |
| --- | --- | --- |
| New Search Session | Polling | Lists sessions. Free. |
| New Property Monitor | Polling | Lists monitors. |
| New Property in Search Session (Billed) | Polling | Advances a cursor-paginated session and returns the new records. Each record is billed. |
| New Property Monitor Event | Instant (REST hook) | See below. |

### Instant trigger: property monitor events

Turning the Zap on creates a V2 property subscription (monitor) whose delivery is `{type: "webhook", url: <the Zap's own hook URL>}`; turning it off deletes that subscription (`DELETE /api/v2/property-subscription/{id}`). The user cannot enter a callback URL anywhere. Each event is `{eventId, propertyId, parcelHash, addressHash, addedToSubscriptionIds, removedFromSubscriptionIds, propertyChangedAt, sentAt}`; `eventId` is the dedupe key. The event carries IDs only. Turn on **Include Property Record (Billed)** (off by default) to add the full record through a Property Lookup, which bills per matched property.

Monitor limits (also in every monitor module description): up to 5 monitors per team, monitors cannot be edited after creation, a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event). Because of the 5-monitor limit, avoid many Zaps that each create a monitor.

**Create Property Monitor** is limited to stream delivery (Kinesis or Azure Event Hubs). The webhook delivery option is used only by the trigger above.

### API Request (Advanced)

Sends any `/api/...` request with the connected token. It refuses, before any call is made: paths containing `wallet` (account balance and billing), asynchronous (`async`) endpoints, creating a monitor (use the dedicated module), non-`/api/` paths, traversal and encoded variants. `webhookUrl` and `errorWebhookUrl` keys are removed from the body at any depth.

- **Triggers.** BatchData's event delivery today is search sessions (billed per record while they drain a frozen result set, capped per team, and with no way to clean up when a Zap is turned off) and the V2 property-subscription webhook (payload not yet documented). Neither is safe to turn into a REST hook yet. A trigger is planned once there is a documented, cleanly cancellable event source.
- **Callback URLs** (`webhookUrl`, `errorWebhookUrl`). These apply only to BatchData's asynchronous requests, not the synchronous endpoints used here. They are never exposed and are removed from the Additional Search Criteria JSON.
- **Wallet and account-balance endpoints.** Never exposed.
- **Custom API call.** Zapier has no generic request module for a custom app. Use Zapier's own Webhooks or API Request step with a BatchData token for other endpoints.
- Reverse skip trace, permits, investor and monitor endpoints, and the asynchronous endpoints are follow-ups.
## Not exposed

- **Callback URLs** (`webhookUrl`, `errorWebhookUrl`) on asynchronous requests. Never exposed and stripped from JSON inputs.
- **Wallet and billing endpoints.** Never exposed.
- **Asynchronous endpoints.** Not exposed.
- A **custom monitor webhook URL.** Only the trigger's own hook URL is ever used.

## Development

```bash
# Node 22 or newer is required by the Zapier CLI
npm install
npm test # node:test unit tests, plus the Zapier harness tests once packages are installed
npx zapier validate # schema and publishing checks
npx zapier test # the same tests through the Zapier CLI
npm test # node:test unit tests with mocked HTTP
npx zapier-platform validate # schema and publishing checks
```

Layout:
Expand All @@ -65,7 +104,10 @@ authentication.js API key auth and connection test
middleware.js Bearer header and error mapping (401 reconnect, 429 throttle, 4xx/5xx messages)
lib/operations.js the 7 operations: fields, request builders, response extraction
lib/builders.js pure request body builders
lib/modules.js turns operations into searches and actions
lib/modules.js turns operations into searches and actions, merges the catalog modules
lib/catalog.json shared declarative catalog (v0.2 modules), identical in the n8n and Make repos
lib/catalog-modules.js turns catalog entries into searches, creates and triggers
lib/engine.js catalog request builder and response extractor (copy of shared/engine.cjs)
samples/ trimmed sample records (regenerate with `npm run samples`)
test/ mocked-HTTP tests; fixtures are derived from the BatchData OpenAPI schemas
```
Expand All @@ -74,14 +116,32 @@ test/ mocked-HTTP tests; fixtures are derived from the BatchData O

## Publishing

1. Create a Zapier developer account with the BatchData company email.
2. Authenticate the Zapier CLI with that account (`npx zapier login`).
3. `npx zapier register "BatchData"`, then `npx zapier push`. Commit the generated `.zapierapprc`.
4. Add a logo (256x256 PNG) and the app description in the Zapier platform UI, add a test account, and submit for review (`npx zapier promote 0.1.0`).
5. Zapier's review needs a working test account, so BatchData must provide an API token for it.
Exact steps (the CLI needs Node 22 or newer). Use the Zapier developer account that will own the app. A deploy key is enough; no GitHub repository is needed.

```bash
cd batchdata-zapier
npm install
npx zapier-platform login # interactive; or export ZAPIER_DEPLOY_KEY=<deploy key> for non-interactive use
npx zapier-platform register "BatchData" # first time only; creates .zapierapprc (commit it)
npx zapier-platform validate
npm test
npx zapier-platform push # uploads the version in package.json (0.2.0)
npx zapier-platform invite you@example.com # optional: test with a few users first
npx zapier-platform promote 0.2.0 # starts review for public listing
```

Before promoting: add a test account (a sandbox API token from app.batchdata.com, which returns mock data at no cost), and review the listing text in the Zapier editor. The icon is `logo.png` (256x256, from the official BatchData logo). The first push must use a version that is not already live.

## What was and was not verified

- Unit tests with mocked HTTP: request bodies, endpoints, response mapping, error mapping, and sample/outputFields consistency. These pass.
- `zapier validate` and `zapier test` have not been run, because the Zapier packages could not be installed in the build environment. The harness tests in `test/zapier-app.test.js` run only once the packages are installed. Run both before pushing.
- Nothing has been called against the live BatchData API. The mocked tests prove request shape and output mapping from the OpenAPI schemas, not live behavior.
Verified live against the production API with the real token (1 record per call): property search, lookup, skip trace V1 and V3, reverse skip trace (no match), address verify, DNC, permits, investor buy box and properties, geocode, reverse geocode, autocomplete, and the search-session create, list and delete lifecycle.

Not verified live (mocked only): property monitors and the instant trigger. The production token used gets 403 on the subscription endpoints. A sandbox token or a token with that ability is needed to verify create, list, delete and event delivery. The webhook delivery shape follows the BatchData product documentation.

Tests: unit tests with mocked HTTP cover request shape, response mapping, guards and hooks. `zapier-platform validate` passes with no errors (warnings only).

**Connection test.** `POST /api/v1/address/verify` with an empty `requests` array returns HTTP 400 ("The requests field is required.") for a valid token. Whether a rejected request is billed could not be proven; there is no zero-cost check for production tokens.

## License

The code in this repository is released under the MIT License (see `LICENSE`). The BatchData API and the data it returns are proprietary to BatchData (batchdata.io) and are subject to BatchData's terms of service; the MIT License does not cover them. Support: support@batchservice.com
57 changes: 57 additions & 0 deletions authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const { API_HOST, SUPPORT_EMAIL } = require('./lib/constants');

// The connection test posts an EMPTY address batch. A valid token gets HTTP 400
// (empty batch rejected before any lookup). An invalid token gets 401 or 403.
// The claim that a rejected empty batch is not billed is assumed, not confirmed.
const test = async (z) => {
const response = await z.request({
url: `${API_HOST}/api/v1/address/verify`,
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: { requests: [] },
skipThrowForStatus: true,
});
const status = response.status;
if (status === 400 || (status >= 200 && status < 300)) {
return { ok: true };
}
if (status === 401) {
throw new z.errors.Error(
'The API token was not accepted. Check the token in your BatchData account settings.',
'AuthenticationError',
401,
);
}
if (status === 403) {
throw new z.errors.Error(
`This token is not allowed to use the BatchData API. Contact ${SUPPORT_EMAIL} if you think this is a mistake.`,
'AuthenticationError',
403,
);
}
if (status === 429) {
throw new z.errors.ThrottledError('BatchData rate limit reached. Try again shortly.');
}
throw new z.errors.Error(
`BatchData returned an unexpected status (${status}) while testing the connection.`,
'UnexpectedStatus',
status,
);
};

module.exports = {
type: 'custom',
fields: [
{
key: 'api_key',
label: 'API Token',
type: 'password',
required: true,
helpText:
'Create an API token in your BatchData account (BatchData dashboard > API). Use a token with access to the endpoints you plan to use. Each request is billed to your account.',
},
],
test,
};
20 changes: 20 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

const { version } = require('./package.json');
const { version: platformVersion } = require('zapier-platform-core');

const authentication = require('./authentication');
const { addBearerToken, handleErrors } = require('./middleware');
const { searches, creates, triggers } = require('./lib/modules');

module.exports = {
version,
platformVersion,
flags: { cleanInputData: false },
authentication,
beforeRequest: [addBearerToken],
afterResponse: [handleErrors],
triggers,
searches,
creates,
};
Loading
Loading