diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f133560 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,15 @@ +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: node scripts/generate.mjs && git diff --exit-code + - run: npm test diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..b50584f --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,58 @@ +# Development notes + +This is a Make custom app in the local project format used by the Make Apps Editor extension for VS Code (`makecomapp.json`, `general/`, `connections/`, `modules/`). + +## Layout + +- `scripts/generate.mjs` writes every JSON file under `general/`, `connections/`, `modules/`, `webhooks/`, `rpcs/` and `makecomapp.json` from `scripts/params.mjs`, the fixtures in `test/fixtures/` and the shared operation catalog. `scripts/catalog.json` is a vendored copy of the catalog used by the Zapier and n8n apps (`scripts/catalog.sha256` pins it; a test fails on drift) and `scripts/catalog-modules.mjs` translates it into modules, the webhook and the dynamic dropdowns. The generated files are committed. Edit the scripts, then run `node scripts/generate.mjs`. +- `test/catalog.test.mjs` checks every catalog module (request, output, TCPA flag, billing text, guards, webhook attach and detach). +- `test/structure.test.mjs` checks the manifest, file links, billing and compliance copy, and that no callback or wallet fields exist. +- `test/replay.test.mjs` renders the request templates with a small IML evaluator (`test/iml.mjs`) and compares the request bodies and output mapping to the n8n and Zapier integrations. + +``` +node --test "test/*.test.mjs" +``` + +Node 20 or later. There are no dependencies. + +## Import into Make + +1. Create a Make account (the organisation that will own the app) and a Make API key with the `apps:read` and `apps:write` scopes. +2. Install the Make Apps Editor extension in VS Code and open this folder. +3. Put the API key in `.secrets/apikey` (the folder is git-ignored). +4. In `makecomapp.json`, set `origins[0].baseUrl` to your zone, for example `https://us1.make.com/api` or `https://eu1.make.com/api`. +5. Run **Deploy to Make** from the extension. The app is created as a private app with id `batchdata`. +6. Open the app in Make and run the checks below. + +## What was and was not verified + +Verified here (offline): JSON validity, manifest links, request bodies and output mapping against mocked BatchData responses, billing and compliance copy, no callback or wallet fields. + +Not verified, needs a Make account and a BatchData test token (a sandbox token returns mock data at no cost): + +- Import with the VS Code extension (the file layout follows public examples, not a Make validator). +- Connection check. It POSTs `{"requests":[]}` to `/v1/address/verify` and marks the connection valid on HTTP 400 or 2xx through `response.valid`. If Make marks the 400 as an error before it reads `valid`, switch the check to `GET https://api.batchdata.com/api/v2/property/search-sessions?take=1` (valid on 2xx). Also confirm with BatchData that the rejected empty request is not billed. +- Optional fields. The templates use `ifempty(x, undefined)` and `omit()` so empty fields are not sent. Nested optional filters (for example Permit Status) are collection parameters that are dropped when empty. Check that an unfilled nested collection (for example Building) is not sent as `{}`. +- Instant triggers. Check that turning a scenario on creates the property subscription (attach), that a test event arrives with `eventId` set, and that turning it off deletes the subscription (detach, `externalHookId`). Create hooks only with a sandbox token: a subscription counts towards the team's limit of 5 and cannot be edited. +- Polling triggers (`Watch New Search Sessions`, `Watch New Properties in a Search Session`): confirm the date ordering and that a first run does not replay old items as expected. +- Dynamic dropdowns (`rpc://listSearchSessions`, `rpc://listMonitors`): confirm the label and value mapping. +- The `{{...}}` spread in the Search Properties output (adds `searchMeta` next to each property). +- Make an API Call: the wallet guard redirects any path containing `wallet` to a non-existent path. IML cannot raise a validation error, so this is best effort. Decide whether to keep the module. + +- Webhook dedupe. The webhook sets `uid` to `body.eventId`; confirm in Make that a repeated event does not start a second run. +- Callback keys. IML cannot strip keys recursively. Make an API Call blocks (sends nothing) any body that contains `webhook` or a JSON unicode escape (`u00`). The Additional Criteria JSON fields on search, session and monitor modules are not stripped; BatchData's synchronous endpoints ignore callback keys, and the monitor and trigger modules fix `deliveryConfig` themselves. A Make custom function that strips the keys would close this gap. + +## Decisions + +- Triggers: the instant triggers use a BatchData webhook subscription (`deliveryConfig {type: "webhook"}`) created on attach and deleted on detach; the hook URL is the scenario's own and is never entered by the user. Polling triggers cover new search sessions and new properties in a session (billed, opt-in by choosing that module). Billed property hydration is a separate module so the default instant trigger never bills a lookup. There is no new-monitor polling trigger (no date or ordered ID). +- Monitor creation (`Create Property Monitor`) is stream delivery only; Make an API Call blocks monitor create or edit calls so webhook URLs only come from the trigger. +- No callback URL fields (`webhookUrl`, `errorWebhookUrl`): they are for asynchronous requests only. +- Skip trace is two modules because V1 and V3 return different shapes. The TCPA option is always sent explicitly (V1 excludes flagged numbers by default, V3 includes them). +- Search sends one request of at most 100 records and does not page, so one run cannot bill more than 100 records. + +## Push steps (Make Apps Editor) + +1. In the Make account that will own the app, create an API key with scopes `apps:read` and `apps:write` (Profile, API access). +2. Install the Make Apps Editor extension in VS Code, open this folder and save the key to `.secrets/apikey`. +3. Set `origins[0].baseUrl` in `makecomapp.json` to the account zone (for example `https://us1.make.com/api`). +4. Run **Deploy to Make**, then the checks above. The app starts private; to publish it publicly submit it for Make review from the app page in Make. diff --git a/LICENSE b/LICENSE index c44fb1d..421d751 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md index f3877ae..c1fb5c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # BatchData for Make -Use BatchData property data inside Make scenarios: search properties, get property details, find owner contact details, verify addresses and check phone numbers. +Use BatchData property data inside Make scenarios: search and look up properties, skip trace owners (V1 and V3, single and batch), reverse skip trace, verify and geocode addresses, check phone numbers, get permits, find investors by buy box, run search sessions and watch property monitors, with polling and instant (webhook) triggers and a generic API call module. + +Support: support@batchservice.com + +> **Status: v0.2.0, not yet imported into Make.** It passes its offline tests. Most operations were smoke-tested live through the BatchData API; monitors and the instant trigger were not (see [What was and was not verified](#what-was-and-was-not-verified)). + +![BatchData](assets/logo.png) ## Connect @@ -11,22 +17,50 @@ If the token is rejected, create a new one. For help, contact support@batchservi ## Modules -| Module | What it does | Billing | -| --- | --- | --- | -| Search Properties | Finds properties by location and filters. Up to 100 records per run. | Per record returned (0 is billed as 1) | -| Get Property Details | Full property data for one address or ID. | Per matched property | -| Skip Trace Owner (V1) | Owner contact details, one result per person. | Per matched property | -| Skip Trace Owner (V3) | Owner contact details, one result per property with a persons list. | Per matched property | -| Verify Address | Standardizes one US address. | Per address | -| Verify Phone Numbers | Reachability, carrier, DNC and TCPA flags. | Per phone number | -| Check Do Not Call | Do Not Call registry check. | Per phone number | -| Check TCPA Litigator | TCPA litigator list check. | Per phone number | -| Make an API Call | Any other BatchData API path. | Follows the endpoint | +Billing text is also in each module's description in Make. + +| Module | Type | What it does | Billing | +| --- | --- | --- | --- | +| Search Properties | Search | Finds properties by location and filters. Up to 100 records per run. | Per record returned (0 is billed as 1) | +| Get Property Details | Search | Full property data for one address or ID. | Per matched property | +| Batch Look Up Properties | Search | Full records for up to 100 addresses in one call. | Per matched property | +| Skip Trace Owner (V1) | Search | Owner contact details, one result per person. | Per matched property | +| Skip Trace Owner (V3) | Search | Owner contact details, one result per property with a persons list. | Per matched property | +| Batch Skip Trace Properties (V3) | Search | Up to 100 properties in one call. | Per matched property | +| Reverse Skip Trace (Phone or Email) | Search | Finds the person behind a phone number or email. | Per matched result | +| Verify Address / Batch Verify Addresses | Search | Standardizes one or up to 100 US addresses. | Per address | +| Autocomplete, Geocode, Reverse Geocode | Search | Address suggestions, coordinates, nearest addresses. | This call does not bill property records | +| Verify Phone Numbers, Check Do Not Call, Check TCPA Litigator | Search | Phone checks. | Per phone number | +| Get Property Permits | Search | Building permits for one property, one result per permit. | See the module description | +| Find Investors (Buy Box), Get Investor Properties | Search | Investors who bought near a subject property, and their holdings. | See the module description | +| List, Create or Advance, Delete Search Session | Search, Action | Persistent search sessions that return only properties not yet delivered. | Create or Advance bills each record delivered | +| List, Get, Create, Delete Property Monitor | Search, Action | Property monitors with stream delivery (Kinesis or Azure Event Hubs). | According to your BatchData plan | +| Watch New Search Sessions | Polling trigger | New search sessions on your team. | Does not bill property records | +| Watch New Properties in a Search Session (Billed) | Polling trigger | New properties delivered by a session. | Every check bills each property delivered | +| Watch Property Monitor Events (Instant) | Instant trigger | Fires when a property is added to, removed from or changed in a monitor. | According to your BatchData plan | +| Watch Property Monitor Events with Property Record (Instant, Billed) | Instant trigger | Same, plus the full property record. | Plan billing, plus one property lookup per event | +| Make an API Call | Universal | Any other BatchData API path. Wallet, asynchronous and monitor create or edit paths are blocked, and so are bodies with callback URL fields. | Follows the endpoint | + +### Instant triggers (property monitor events) + +Turning a scenario on creates a V2 property subscription whose delivery is the scenario's own Make webhook URL. Turning it off (or deleting the scenario) deletes that subscription. You never enter a callback URL. Each event is `{eventId, propertyId, parcelHash, addressHash, addedToSubscriptionIds, removedFromSubscriptionIds, propertyChangedAt, sentAt}`. The event carries IDs only; use the module with **Property Record** to add the full record (one billed property lookup per event). Use `eventId` to skip repeats. + +Limits (also in each trigger's description): up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), 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 scenarios that each create a monitor. + +There is no polling "new monitor" trigger in Make: a polling trigger needs a date or an ordered ID on each item, and monitors have neither. Use List Property Monitors on a schedule instead. The Zapier and n8n apps do include one. ## Phone compliance Skip trace and phone modules return data only. You are responsible for following the TCPA and Do Not Call rules before you contact anyone. Skip trace removes phone numbers flagged as TCPA litigators unless you switch on **Include phone numbers flagged as TCPA litigators**. -## Not included +## What was and was not verified + +Verified live (production API, 1 record per call, through the same request shapes the other BatchData integrations use): 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. + +Mocked only: property monitors and the instant triggers. The token used gets 403 on the subscription endpoints, so create, list, delete and event delivery need a sandbox token (mock data, no cost, from app.batchdata.com) or a token with that ability. + +Offline tests (47) check the manifest, file links, copy rules (one support contact, billing text in every module, no callback fields) and the request body, query and output of every module against mocked responses. The offline IML evaluator is a subset of Make's engine, so Make itself has not run these modules; see `DEVELOPMENT.md` for the checks to run after import. + +## License -There is no instant trigger (watch) module in this version. Use a scheduled scenario with Search Properties instead. +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 diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..794f2e6 Binary files /dev/null and b/assets/logo.png differ diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..606c663 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/connections/batchdata/batchdata.communication.iml.json b/connections/batchdata/batchdata.communication.iml.json new file mode 100644 index 0000000..30347b4 --- /dev/null +++ b/connections/batchdata/batchdata.communication.iml.json @@ -0,0 +1,33 @@ +{ + "url": "https://api.batchdata.com/api/v1/address/verify", + "method": "POST", + "headers": { + "Authorization": "Bearer {{parameters.apiKey}}", + "Content-Type": "application/json" + }, + "body": { + "requests": [] + }, + "response": { + "valid": "{{statusCode == 400 || (statusCode >= 200 && statusCode < 300)}}", + "data": { + "apiKey": "{{parameters.apiKey}}" + }, + "error": { + "401": { + "type": "InvalidAccessTokenError", + "message": "Invalid API token. Copy a fresh token from your BatchData account." + }, + "403": { + "type": "RuntimeError", + "message": "This token cannot use the BatchData API. Contact support@batchservice.com." + }, + "message": "[{{statusCode}}] {{ifempty(body.status.message, 'Could not verify the API token.')}}" + } + }, + "log": { + "sanitize": [ + "request.headers.authorization" + ] + } +} diff --git a/connections/batchdata/batchdata.params.iml.json b/connections/batchdata/batchdata.params.iml.json new file mode 100644 index 0000000..5526b48 --- /dev/null +++ b/connections/batchdata/batchdata.params.iml.json @@ -0,0 +1,9 @@ +[ + { + "name": "apiKey", + "type": "password", + "label": "API token", + "required": true, + "help": "Create an API token in your BatchData account and paste it here. Tokens are only available on plans with API access." + } +] diff --git a/general/base.iml.json b/general/base.iml.json new file mode 100644 index 0000000..7e647f4 --- /dev/null +++ b/general/base.iml.json @@ -0,0 +1,34 @@ +{ + "baseUrl": "https://api.batchdata.com/api", + "headers": { + "Authorization": "Bearer {{connection.apiKey}}", + "Content-Type": "application/json" + }, + "response": { + "error": { + "400": { + "type": "DataError", + "message": "[400] {{ifempty(body.status.message, 'The request was rejected. Check the values you entered.')}}" + }, + "401": { + "type": "InvalidAccessTokenError", + "message": "Your BatchData API token is invalid or expired. Create a new connection with a valid token." + }, + "403": { + "type": "RuntimeError", + "message": "Your BatchData API token does not have access to this data. Contact support@batchservice.com to enable it." + }, + "429": { + "type": "RateLimitError", + "message": "The BatchData rate limit was reached. Wait a moment and try again." + }, + "message": "[{{statusCode}}] {{ifempty(body.status.message, ifempty(body.status.text, 'The BatchData API returned an unexpected error.'))}}", + "type": "RuntimeError" + } + }, + "log": { + "sanitize": [ + "request.headers.authorization" + ] + } +} diff --git a/general/common.json b/general/common.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/general/common.json @@ -0,0 +1 @@ +{} diff --git a/makecomapp.json b/makecomapp.json new file mode 100644 index 0000000..0b102b2 --- /dev/null +++ b/makecomapp.json @@ -0,0 +1,594 @@ +{ + "fileVersion": 1, + "generalCodeFiles": { + "base": "general/base.iml.json", + "common": "general/common.json", + "readme": "README.md", + "groups": "modules/groups.json" + }, + "components": { + "connection": { + "batchdata": { + "label": "BatchData", + "codeFiles": { + "communication": "connections/batchdata/batchdata.communication.iml.json", + "params": "connections/batchdata/batchdata.params.iml.json" + } + } + }, + "module": { + "searchProperties": { + "moduleType": "search", + "label": "Search Properties", + "description": "Finds properties that match your filters. Billed per record returned. A Max results value of 0 is billed as 1 record. One call returns at most 100 records.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/searchProperties/searchProperties.communication.iml.json", + "mappableParams": "modules/searchProperties/searchProperties.mappable-params.iml.json", + "interface": "modules/searchProperties/searchProperties.interface.iml.json", + "samples": "modules/searchProperties/searchProperties.samples.iml.json" + } + }, + "lookupProperty": { + "moduleType": "action", + "label": "Get Property Details", + "description": "Returns property data for one address or identifier. Billed per matched property. Skip trace, if included, is billed separately.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/lookupProperty/lookupProperty.communication.iml.json", + "mappableParams": "modules/lookupProperty/lookupProperty.mappable-params.iml.json", + "interface": "modules/lookupProperty/lookupProperty.interface.iml.json", + "samples": "modules/lookupProperty/lookupProperty.samples.iml.json" + } + }, + "skipTraceV1": { + "moduleType": "search", + "label": "Skip Trace Owner (V1)", + "description": "Finds owner contact details for a property, one result per person. Billed per matched property. Phone numbers flagged as TCPA litigators are removed unless you turn that option on. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/skipTraceV1/skipTraceV1.communication.iml.json", + "mappableParams": "modules/skipTraceV1/skipTraceV1.mappable-params.iml.json", + "interface": "modules/skipTraceV1/skipTraceV1.interface.iml.json", + "samples": "modules/skipTraceV1/skipTraceV1.samples.iml.json" + } + }, + "skipTraceV3": { + "moduleType": "search", + "label": "Skip Trace Owner (V3)", + "description": "Finds owner contact details using the V3 skip trace, one result per property with a list of persons. Billed per matched property. Phone numbers flagged as TCPA litigators are removed unless you turn that option on. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/skipTraceV3/skipTraceV3.communication.iml.json", + "mappableParams": "modules/skipTraceV3/skipTraceV3.mappable-params.iml.json", + "interface": "modules/skipTraceV3/skipTraceV3.interface.iml.json", + "samples": "modules/skipTraceV3/skipTraceV3.samples.iml.json" + } + }, + "verifyAddress": { + "moduleType": "action", + "label": "Verify Address", + "description": "Standardizes and validates one US address. Billed per address.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/verifyAddress/verifyAddress.communication.iml.json", + "mappableParams": "modules/verifyAddress/verifyAddress.mappable-params.iml.json", + "interface": "modules/verifyAddress/verifyAddress.interface.iml.json", + "samples": "modules/verifyAddress/verifyAddress.samples.iml.json" + } + }, + "verifyPhones": { + "moduleType": "search", + "label": "Verify Phone Numbers", + "description": "Checks whether phone numbers are reachable and returns carrier, DNC and TCPA flags, one result per number. Billed per phone number. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/verifyPhones/verifyPhones.communication.iml.json", + "mappableParams": "modules/verifyPhones/verifyPhones.mappable-params.iml.json", + "interface": "modules/verifyPhones/verifyPhones.interface.iml.json", + "samples": "modules/verifyPhones/verifyPhones.samples.iml.json" + } + }, + "checkDnc": { + "moduleType": "search", + "label": "Check Do Not Call", + "description": "Checks phone numbers against the Do Not Call registry, one result per number. Billed per phone number. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/checkDnc/checkDnc.communication.iml.json", + "mappableParams": "modules/checkDnc/checkDnc.mappable-params.iml.json", + "interface": "modules/checkDnc/checkDnc.interface.iml.json", + "samples": "modules/checkDnc/checkDnc.samples.iml.json" + } + }, + "checkTcpa": { + "moduleType": "search", + "label": "Check TCPA Litigator", + "description": "Checks phone numbers against the TCPA litigator list, one result per number. Billed per phone number. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/checkTcpa/checkTcpa.communication.iml.json", + "mappableParams": "modules/checkTcpa/checkTcpa.mappable-params.iml.json", + "interface": "modules/checkTcpa/checkTcpa.interface.iml.json", + "samples": "modules/checkTcpa/checkTcpa.samples.iml.json" + } + }, + "makeAnApiCall": { + "moduleType": "universal", + "label": "Make an API Call", + "description": "Performs an authorized call to any BatchData API path. Billing follows the endpoint you call. Account balance (wallet) endpoints, asynchronous (callback) endpoints and monitor creation are not available here; use the dedicated modules for those.", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/makeAnApiCall/makeAnApiCall.communication.iml.json", + "mappableParams": "modules/makeAnApiCall/makeAnApiCall.mappable-params.iml.json", + "interface": "modules/makeAnApiCall/makeAnApiCall.interface.iml.json", + "samples": "modules/makeAnApiCall/makeAnApiCall.samples.iml.json" + } + }, + "reverseSkipTrace": { + "moduleType": "search", + "label": "Reverse Skip Trace (Phone or Email)", + "description": "Finds the person (and associated property addresses) behind a phone number or an email address using the V3 reverse skip trace. Returns up to 3 persons. Billed per matched person; requests with no match are not billed. Results are data only. You are responsible for TCPA and Do Not Call compliance. Fill in at least one of: Phone Number, Email Address.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/reverseSkipTrace/reverseSkipTrace.communication.iml.json", + "mappableParams": "modules/reverseSkipTrace/reverseSkipTrace.mappable-params.iml.json", + "interface": "modules/reverseSkipTrace/reverseSkipTrace.interface.iml.json", + "samples": "modules/reverseSkipTrace/reverseSkipTrace.samples.iml.json" + } + }, + "getPermits": { + "moduleType": "search", + "label": "Get Property Permits", + "description": "Returns building permits for one property, one result per permit. Identify the property by address, BatchData property ID, or APN with county FIPS code. Billed per property requested, however many permits come back. Fill in at least one of: Street, BatchData Property ID, APN.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/getPermits/getPermits.communication.iml.json", + "mappableParams": "modules/getPermits/getPermits.mappable-params.iml.json", + "interface": "modules/getPermits/getPermits.interface.iml.json", + "samples": "modules/getPermits/getPermits.samples.iml.json" + } + }, + "batchSkipTrace": { + "moduleType": "action", + "label": "Batch Skip Trace Properties (V3)", + "description": "Skip traces up to 100 properties in one call with the V3 skip trace. Returns one result per submitted property, each with a list of persons. Billed per property matched. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/batchSkipTrace/batchSkipTrace.communication.iml.json", + "mappableParams": "modules/batchSkipTrace/batchSkipTrace.mappable-params.iml.json", + "interface": "modules/batchSkipTrace/batchSkipTrace.interface.iml.json", + "samples": "modules/batchSkipTrace/batchSkipTrace.samples.iml.json" + } + }, + "batchLookupProperty": { + "moduleType": "action", + "label": "Batch Look Up Properties", + "description": "Returns full property records for up to 100 addresses in one call. Billed per property matched.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/batchLookupProperty/batchLookupProperty.communication.iml.json", + "mappableParams": "modules/batchLookupProperty/batchLookupProperty.mappable-params.iml.json", + "interface": "modules/batchLookupProperty/batchLookupProperty.interface.iml.json", + "samples": "modules/batchLookupProperty/batchLookupProperty.samples.iml.json" + } + }, + "autocompleteAddress": { + "moduleType": "search", + "label": "Autocomplete Address", + "description": "Suggests addresses for partial text, one result per suggestion. Billed per call.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/autocompleteAddress/autocompleteAddress.communication.iml.json", + "mappableParams": "modules/autocompleteAddress/autocompleteAddress.mappable-params.iml.json", + "interface": "modules/autocompleteAddress/autocompleteAddress.interface.iml.json", + "samples": "modules/autocompleteAddress/autocompleteAddress.samples.iml.json" + } + }, + "geocodeAddress": { + "moduleType": "search", + "label": "Geocode Address", + "description": "Returns latitude, longitude and the standardized form of one address. Billed per address submitted.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/geocodeAddress/geocodeAddress.communication.iml.json", + "mappableParams": "modules/geocodeAddress/geocodeAddress.mappable-params.iml.json", + "interface": "modules/geocodeAddress/geocodeAddress.interface.iml.json", + "samples": "modules/geocodeAddress/geocodeAddress.samples.iml.json" + } + }, + "reverseGeocode": { + "moduleType": "search", + "label": "Reverse Geocode (Find Nearby Addresses)", + "description": "Returns the nearest addresses to a latitude and longitude, one result per address. Billed per geo-location submitted.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/reverseGeocode/reverseGeocode.communication.iml.json", + "mappableParams": "modules/reverseGeocode/reverseGeocode.mappable-params.iml.json", + "interface": "modules/reverseGeocode/reverseGeocode.interface.iml.json", + "samples": "modules/reverseGeocode/reverseGeocode.samples.iml.json" + } + }, + "batchVerifyAddress": { + "moduleType": "action", + "label": "Batch Verify Addresses", + "description": "Standardizes and validates up to 100 US addresses in one call. Billed per address submitted.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/batchVerifyAddress/batchVerifyAddress.communication.iml.json", + "mappableParams": "modules/batchVerifyAddress/batchVerifyAddress.mappable-params.iml.json", + "interface": "modules/batchVerifyAddress/batchVerifyAddress.interface.iml.json", + "samples": "modules/batchVerifyAddress/batchVerifyAddress.samples.iml.json" + } + }, + "investorBuybox": { + "moduleType": "search", + "label": "Find Investors (Buy Box)", + "description": "Finds likely real estate investors for a subject property from nearby comparable sales, one result per investor with holdings, acquisitions and contacts. Billed according to your BatchData plan; contact support@batchservice.com for pricing. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/investorBuybox/investorBuybox.communication.iml.json", + "mappableParams": "modules/investorBuybox/investorBuybox.mappable-params.iml.json", + "interface": "modules/investorBuybox/investorBuybox.interface.iml.json", + "samples": "modules/investorBuybox/investorBuybox.samples.iml.json" + } + }, + "investorProperties": { + "moduleType": "search", + "label": "Get Investor Properties", + "description": "Returns the properties of one investor, one result per property. Get the investor ID from Find Investors (Buy Box). Use the \"nextPageCursor\" value of the last result as Page Cursor to read the next page. Billed per property row returned (take sets the maximum cost of a call).", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/investorProperties/investorProperties.communication.iml.json", + "mappableParams": "modules/investorProperties/investorProperties.mappable-params.iml.json", + "interface": "modules/investorProperties/investorProperties.interface.iml.json", + "samples": "modules/investorProperties/investorProperties.samples.iml.json" + } + }, + "listSearchSessions": { + "moduleType": "search", + "label": "List Search Sessions", + "description": "Lists your team's search sessions, oldest first. Needs a token with the property search sessions permission. This call does not bill property records.", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/listSearchSessions/listSearchSessions.communication.iml.json", + "mappableParams": "modules/listSearchSessions/listSearchSessions.mappable-params.iml.json", + "interface": "modules/listSearchSessions/listSearchSessions.interface.iml.json", + "samples": "modules/listSearchSessions/listSearchSessions.samples.iml.json" + } + }, + "createSearchSession": { + "moduleType": "action", + "label": "Create or Advance Search Session", + "description": "Runs a property search inside a persistent session and returns the next properties that session has not delivered yet. The first call creates the session; calling again with the same Session ID continues it. Billed per property record delivered; a session never delivers (or bills) the same property twice. Each call bills up to Max Results records.", + "actionCrud": "create", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/createSearchSession/createSearchSession.communication.iml.json", + "mappableParams": "modules/createSearchSession/createSearchSession.mappable-params.iml.json", + "interface": "modules/createSearchSession/createSearchSession.interface.iml.json", + "samples": "modules/createSearchSession/createSearchSession.samples.iml.json" + } + }, + "deleteSearchSession": { + "moduleType": "action", + "label": "Delete Search Session", + "description": "Deletes a search session to free one of your 100 session slots. Deleting a session starts delivery over if you use the same ID again. This call does not bill property records.", + "actionCrud": "delete", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/deleteSearchSession/deleteSearchSession.communication.iml.json", + "mappableParams": "modules/deleteSearchSession/deleteSearchSession.mappable-params.iml.json", + "interface": "modules/deleteSearchSession/deleteSearchSession.interface.iml.json", + "samples": "modules/deleteSearchSession/deleteSearchSession.samples.iml.json" + } + }, + "listMonitors": { + "moduleType": "search", + "label": "List Property Monitors", + "description": "Lists your property monitors. Needs a token with the property subscription permission. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/listMonitors/listMonitors.communication.iml.json", + "mappableParams": "modules/listMonitors/listMonitors.mappable-params.iml.json", + "interface": "modules/listMonitors/listMonitors.interface.iml.json", + "samples": "modules/listMonitors/listMonitors.samples.iml.json" + } + }, + "getMonitor": { + "moduleType": "search", + "label": "Get Property Monitor", + "description": "Returns one property monitor with its search criteria and delivery settings (secrets are masked). Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "actionCrud": "read", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/getMonitor/getMonitor.communication.iml.json", + "mappableParams": "modules/getMonitor/getMonitor.mappable-params.iml.json", + "interface": "modules/getMonitor/getMonitor.interface.iml.json", + "samples": "modules/getMonitor/getMonitor.samples.iml.json" + } + }, + "createMonitor": { + "moduleType": "action", + "label": "Create Property Monitor (Stream Delivery)", + "description": "Creates a property monitor that streams changes for matching properties to your own AWS Kinesis stream or Azure Event Hub. For webhook delivery use the \"New Property Monitor Event\" trigger, which sets up and removes its own monitor. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "actionCrud": "create", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/createMonitor/createMonitor.communication.iml.json", + "mappableParams": "modules/createMonitor/createMonitor.mappable-params.iml.json", + "interface": "modules/createMonitor/createMonitor.interface.iml.json", + "samples": "modules/createMonitor/createMonitor.samples.iml.json" + } + }, + "deleteMonitor": { + "moduleType": "action", + "label": "Delete Property Monitor", + "description": "Deletes a property monitor so it stops delivering events. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "actionCrud": "delete", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/deleteMonitor/deleteMonitor.communication.iml.json", + "mappableParams": "modules/deleteMonitor/deleteMonitor.mappable-params.iml.json", + "interface": "modules/deleteMonitor/deleteMonitor.interface.iml.json", + "samples": "modules/deleteMonitor/deleteMonitor.samples.iml.json" + } + }, + "watchSearchSessions": { + "moduleType": "trigger", + "label": "Watch New Search Sessions", + "description": "Triggers when a new search session appears on your team. Needs a token with the property search sessions permission. This call does not bill property records.", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/watchSearchSessions/watchSearchSessions.communication.iml.json", + "staticParams": "modules/watchSearchSessions/watchSearchSessions.static-params.iml.json", + "interface": "modules/watchSearchSessions/watchSearchSessions.interface.iml.json", + "samples": "modules/watchSearchSessions/watchSearchSessions.samples.iml.json" + } + }, + "watchSessionProperties": { + "moduleType": "trigger", + "label": "Watch New Properties in a Search Session (Billed)", + "description": "Triggers when a search session delivers a property that it has not delivered before. Every check runs the search and bills each property delivered, up to Max Results per check, and a property is never billed twice within the same Session ID. Billed per property record delivered; a session never delivers (or bills) the same property twice. Use a low Max Results while you test. Every scheduled run of the scenario, and every \"Run once\", is a billed search.", + "connection": "batchdata", + "codeFiles": { + "communication": "modules/watchSessionProperties/watchSessionProperties.communication.iml.json", + "staticParams": "modules/watchSessionProperties/watchSessionProperties.static-params.iml.json", + "interface": "modules/watchSessionProperties/watchSessionProperties.interface.iml.json", + "samples": "modules/watchSessionProperties/watchSessionProperties.samples.iml.json" + } + }, + "watchMonitorEvents": { + "moduleType": "instant_trigger", + "label": "Watch Property Monitor Events (Instant)", + "description": "Triggers when BatchData reports that a property matching your criteria was added to or removed from a monitor, or changed (instant). Turning the scenario on creates a monitor that delivers to this trigger's own hook URL; turning it off deletes that monitor. Each event carries the property ID and change details; use \"Watch Property Monitor Events with Property Record\" to also look up the full property record. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "connection": "batchdata", + "webhook": "monitorEvents", + "codeFiles": { + "communication": "modules/watchMonitorEvents/watchMonitorEvents.communication.iml.json", + "interface": "modules/watchMonitorEvents/watchMonitorEvents.interface.iml.json", + "samples": "modules/watchMonitorEvents/watchMonitorEvents.samples.iml.json" + } + }, + "watchMonitorEventsWithProperty": { + "moduleType": "instant_trigger", + "label": "Watch Property Monitor Events with Property Record (Instant, Billed)", + "description": "Triggers when BatchData reports that a property matching your criteria was added to or removed from a monitor, or changed (instant). Turning the scenario on creates a monitor that delivers to this trigger's own hook URL; turning it off deletes that monitor. Each event carries the property ID and change details; use \"Watch Property Monitor Events with Property Record\" to also look up the full property record. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event). This version also looks up the full property record for every event. The lookup is billed per event, per matched property, on top of the monitor charges.", + "connection": "batchdata", + "webhook": "monitorEvents", + "codeFiles": { + "communication": "modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.communication.iml.json", + "interface": "modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.interface.iml.json", + "samples": "modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.samples.iml.json" + } + } + }, + "webhook": { + "monitorEvents": { + "label": "Property monitor events", + "webhookType": "web", + "connection": "batchdata", + "codeFiles": { + "communication": "webhooks/monitorEvents/monitorEvents.communication.iml.json", + "params": "webhooks/monitorEvents/monitorEvents.params.iml.json", + "attach": "webhooks/monitorEvents/monitorEvents.attach.iml.json", + "detach": "webhooks/monitorEvents/monitorEvents.detach.iml.json" + } + } + }, + "rpc": { + "listSearchSessions": { + "label": "List search sessions", + "connection": "batchdata", + "codeFiles": { + "communication": "rpcs/listSearchSessions/listSearchSessions.communication.iml.json", + "params": "rpcs/listSearchSessions/listSearchSessions.params.iml.json" + } + }, + "listMonitors": { + "label": "List property monitors", + "connection": "batchdata", + "codeFiles": { + "communication": "rpcs/listMonitors/listMonitors.communication.iml.json", + "params": "rpcs/listMonitors/listMonitors.params.iml.json" + } + } + } + }, + "origins": [ + { + "label": "Make (set your zone)", + "baseUrl": "https://eu1.make.com/api", + "appId": "batchdata", + "appVersion": 1, + "apikeyFile": ".secrets/apikey", + "idMapping": { + "connection": [ + { + "local": "batchdata", + "remote": "batchdata" + } + ], + "module": [ + { + "local": "searchProperties", + "remote": "searchProperties" + }, + { + "local": "lookupProperty", + "remote": "lookupProperty" + }, + { + "local": "skipTraceV1", + "remote": "skipTraceV1" + }, + { + "local": "skipTraceV3", + "remote": "skipTraceV3" + }, + { + "local": "verifyAddress", + "remote": "verifyAddress" + }, + { + "local": "verifyPhones", + "remote": "verifyPhones" + }, + { + "local": "checkDnc", + "remote": "checkDnc" + }, + { + "local": "checkTcpa", + "remote": "checkTcpa" + }, + { + "local": "makeAnApiCall", + "remote": "makeAnApiCall" + }, + { + "local": "reverseSkipTrace", + "remote": "reverseSkipTrace" + }, + { + "local": "getPermits", + "remote": "getPermits" + }, + { + "local": "batchSkipTrace", + "remote": "batchSkipTrace" + }, + { + "local": "batchLookupProperty", + "remote": "batchLookupProperty" + }, + { + "local": "autocompleteAddress", + "remote": "autocompleteAddress" + }, + { + "local": "geocodeAddress", + "remote": "geocodeAddress" + }, + { + "local": "reverseGeocode", + "remote": "reverseGeocode" + }, + { + "local": "batchVerifyAddress", + "remote": "batchVerifyAddress" + }, + { + "local": "investorBuybox", + "remote": "investorBuybox" + }, + { + "local": "investorProperties", + "remote": "investorProperties" + }, + { + "local": "listSearchSessions", + "remote": "listSearchSessions" + }, + { + "local": "createSearchSession", + "remote": "createSearchSession" + }, + { + "local": "deleteSearchSession", + "remote": "deleteSearchSession" + }, + { + "local": "listMonitors", + "remote": "listMonitors" + }, + { + "local": "getMonitor", + "remote": "getMonitor" + }, + { + "local": "createMonitor", + "remote": "createMonitor" + }, + { + "local": "deleteMonitor", + "remote": "deleteMonitor" + }, + { + "local": "watchSearchSessions", + "remote": "watchSearchSessions" + }, + { + "local": "watchSessionProperties", + "remote": "watchSessionProperties" + }, + { + "local": "watchMonitorEvents", + "remote": "watchMonitorEvents" + }, + { + "local": "watchMonitorEventsWithProperty", + "remote": "watchMonitorEventsWithProperty" + } + ], + "function": [], + "rpc": [ + { + "local": "listSearchSessions", + "remote": "listSearchSessions" + }, + { + "local": "listMonitors", + "remote": "listMonitors" + } + ], + "webhook": [ + { + "local": "monitorEvents", + "remote": "monitorEvents" + } + ] + } + } + ] +} diff --git a/modules/autocompleteAddress/autocompleteAddress.communication.iml.json b/modules/autocompleteAddress/autocompleteAddress.communication.iml.json new file mode 100644 index 0000000..d36db0b --- /dev/null +++ b/modules/autocompleteAddress/autocompleteAddress.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v1/address/autocomplete", + "method": "POST", + "body": { + "searchCriteria": { + "query": "{{parameters.query}}" + }, + "options": { + "take": "{{ifempty(parameters.take, 5)}}", + "uspsVerifiedAddresses": "{{if(parameters.uspsVerifiedOnly == true, true, undefined)}}" + } + }, + "response": { + "iterate": "{{body.result.suggestions}}", + "output": "{{item}}" + } +} diff --git a/modules/autocompleteAddress/autocompleteAddress.interface.iml.json b/modules/autocompleteAddress/autocompleteAddress.interface.iml.json new file mode 100644 index 0000000..a34cb08 --- /dev/null +++ b/modules/autocompleteAddress/autocompleteAddress.interface.iml.json @@ -0,0 +1,57 @@ +[ + { + "name": "propertyId", + "type": "text", + "label": "Property id" + }, + { + "name": "name", + "type": "text", + "label": "Name" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "address", + "type": "text", + "label": "Address" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "propertyCount", + "type": "number", + "label": "Property count" + }, + { + "name": "uspsVerified", + "type": "boolean", + "label": "Usps verified" + } +] diff --git a/modules/autocompleteAddress/autocompleteAddress.mappable-params.iml.json b/modules/autocompleteAddress/autocompleteAddress.mappable-params.iml.json new file mode 100644 index 0000000..b62f11e --- /dev/null +++ b/modules/autocompleteAddress/autocompleteAddress.mappable-params.iml.json @@ -0,0 +1,25 @@ +[ + { + "name": "query", + "label": "Partial Address", + "required": true, + "help": "For example \"2800 N 24th\".", + "type": "text" + }, + { + "name": "take", + "label": "Max Results", + "help": "Whole number from 1 to 20. Default 5.", + "type": "integer", + "default": 5, + "validate": { + "min": 1, + "max": 20 + } + }, + { + "name": "uspsVerifiedOnly", + "label": "USPS-Verified Addresses Only", + "type": "boolean" + } +] diff --git a/modules/autocompleteAddress/autocompleteAddress.samples.iml.json b/modules/autocompleteAddress/autocompleteAddress.samples.iml.json new file mode 100644 index 0000000..6a631d8 --- /dev/null +++ b/modules/autocompleteAddress/autocompleteAddress.samples.iml.json @@ -0,0 +1,13 @@ +{ + "propertyId": "09201d3bd3134f9380704014781fb4c0", + "name": "2800 N 24th St, Phoenix, AZ 85008", + "type": "address", + "address": "2800 n 24th st", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "latitude": 33.478332, + "longitude": -112.030474, + "propertyCount": 1, + "uspsVerified": true +} diff --git a/modules/batchLookupProperty/batchLookupProperty.communication.iml.json b/modules/batchLookupProperty/batchLookupProperty.communication.iml.json new file mode 100644 index 0000000..fdc632b --- /dev/null +++ b/modules/batchLookupProperty/batchLookupProperty.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v1/property/lookup/all-attributes", + "method": "POST", + "body": { + "requests": "{{parameters.addresses}}", + "options": { + "datasets": "{{if(length(parameters.datasets) > 0, parameters.datasets, undefined)}}" + } + }, + "response": { + "output": { + "properties": "{{body.results.properties}}", + "matchCount": "{{body.results.meta.results.matchCount}}", + "noMatchCount": "{{body.results.meta.results.noMatchCount}}" + } + } +} diff --git a/modules/batchLookupProperty/batchLookupProperty.interface.iml.json b/modules/batchLookupProperty/batchLookupProperty.interface.iml.json new file mode 100644 index 0000000..c7aa8fa --- /dev/null +++ b/modules/batchLookupProperty/batchLookupProperty.interface.iml.json @@ -0,0 +1,80 @@ +[ + { + "name": "properties", + "type": "array", + "label": "Properties", + "spec": [ + { + "name": "_id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "county", + "type": "text", + "label": "County" + } + ] + }, + { + "name": "general", + "type": "collection", + "label": "General", + "spec": [ + { + "name": "propertyTypeCategory", + "type": "text", + "label": "Property type category" + } + ] + }, + { + "name": "quickLists", + "type": "collection", + "label": "Quick lists", + "spec": [ + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + } + ] + } + ] + }, + { + "name": "matchCount", + "type": "number", + "label": "Match count" + }, + { + "name": "noMatchCount", + "type": "number", + "label": "No match count" + } +] diff --git a/modules/batchLookupProperty/batchLookupProperty.mappable-params.iml.json b/modules/batchLookupProperty/batchLookupProperty.mappable-params.iml.json new file mode 100644 index 0000000..3a99ba2 --- /dev/null +++ b/modules/batchLookupProperty/batchLookupProperty.mappable-params.iml.json @@ -0,0 +1,110 @@ +[ + { + "name": "addresses", + "label": "Addresses", + "required": true, + "help": "One row per property address, up to 100.", + "type": "array", + "spec": [ + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "ZIP Code" + } + ] + }, + { + "name": "requestId", + "type": "text", + "label": "Your Reference ID" + } + ] + }, + { + "name": "datasets", + "label": "Datasets", + "help": "Optional. Limits the response to these datasets. \"basic\" and \"core\" cannot be combined.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "basic", + "value": "basic" + }, + { + "label": "comps", + "value": "comps" + }, + { + "label": "batchrank", + "value": "batchrank" + }, + { + "label": "contact", + "value": "contact" + }, + { + "label": "core", + "value": "core" + }, + { + "label": "deed", + "value": "deed" + }, + { + "label": "demographic", + "value": "demographic" + }, + { + "label": "foreclosure", + "value": "foreclosure" + }, + { + "label": "listing", + "value": "listing" + }, + { + "label": "mortgage-liens", + "value": "mortgage-liens" + }, + { + "label": "owner", + "value": "owner" + }, + { + "label": "permit", + "value": "permit" + }, + { + "label": "quicklist", + "value": "quicklist" + }, + { + "label": "valuation", + "value": "valuation" + } + ] + } + } +] diff --git a/modules/batchLookupProperty/batchLookupProperty.samples.iml.json b/modules/batchLookupProperty/batchLookupProperty.samples.iml.json new file mode 100644 index 0000000..d8ed48f --- /dev/null +++ b/modules/batchLookupProperty/batchLookupProperty.samples.iml.json @@ -0,0 +1,22 @@ +{ + "properties": [ + { + "_id": "09201d3bd3134f9380704014781fb4c0", + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa" + }, + "general": { + "propertyTypeCategory": "Single Family" + }, + "quickLists": { + "vacant": false + } + } + ], + "matchCount": 1, + "noMatchCount": 0 +} diff --git a/modules/batchSkipTrace/batchSkipTrace.communication.iml.json b/modules/batchSkipTrace/batchSkipTrace.communication.iml.json new file mode 100644 index 0000000..0262d6b --- /dev/null +++ b/modules/batchSkipTrace/batchSkipTrace.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v3/property/skip-trace", + "method": "POST", + "body": { + "requests": "{{parameters.properties}}", + "options": { + "includeTCPABlacklistedPhones": "{{if(parameters.includeTcpaFlagged == true, true, false)}}" + } + }, + "response": { + "output": { + "results": "{{body.result.data}}", + "matchCount": "{{body.result.meta.results.matchCount}}", + "requestCount": "{{body.result.meta.results.requestCount}}" + } + } +} diff --git a/modules/batchSkipTrace/batchSkipTrace.interface.iml.json b/modules/batchSkipTrace/batchSkipTrace.interface.iml.json new file mode 100644 index 0000000..b7c8578 --- /dev/null +++ b/modules/batchSkipTrace/batchSkipTrace.interface.iml.json @@ -0,0 +1,89 @@ +[ + { + "name": "results", + "type": "array", + "label": "Results", + "spec": [ + { + "name": "input", + "type": "collection", + "label": "Input", + "spec": [ + { + "name": "requestId", + "type": "text", + "label": "Request id" + } + ] + }, + { + "name": "persons", + "type": "array", + "label": "Persons", + "spec": [ + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + } + ] + }, + { + "name": "phoneNumbers", + "type": "array", + "label": "Phone numbers", + "spec": [ + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "tcpa", + "type": "boolean", + "label": "Tcpa" + } + ] + } + ] + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "matched", + "type": "boolean", + "label": "Matched" + } + ] + } + ] + }, + { + "name": "matchCount", + "type": "number", + "label": "Match count" + }, + { + "name": "requestCount", + "type": "number", + "label": "Request count" + } +] diff --git a/modules/batchSkipTrace/batchSkipTrace.mappable-params.iml.json b/modules/batchSkipTrace/batchSkipTrace.mappable-params.iml.json new file mode 100644 index 0000000..a43d1e2 --- /dev/null +++ b/modules/batchSkipTrace/batchSkipTrace.mappable-params.iml.json @@ -0,0 +1,67 @@ +[ + { + "name": "properties", + "label": "Properties", + "required": true, + "help": "One row per property, up to 100. Billed per matched property.", + "type": "array", + "spec": [ + { + "name": "propertyAddress", + "type": "collection", + "label": "Property address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "ZIP Code" + } + ] + }, + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "Owner First Name" + }, + { + "name": "last", + "type": "text", + "label": "Owner Last Name" + } + ] + }, + { + "name": "requestId", + "type": "text", + "label": "Your Reference ID" + } + ] + }, + { + "name": "includeTcpaFlagged", + "label": "Include TCPA-Flagged Phones", + "help": "Off by default: phone numbers flagged as TCPA litigators are left out. Turn on only with a documented compliance basis. Flagged numbers carry \"tcpa\": true in the result.", + "type": "boolean", + "default": false + } +] diff --git a/modules/batchSkipTrace/batchSkipTrace.samples.iml.json b/modules/batchSkipTrace/batchSkipTrace.samples.iml.json new file mode 100644 index 0000000..66c5b6d --- /dev/null +++ b/modules/batchSkipTrace/batchSkipTrace.samples.iml.json @@ -0,0 +1,29 @@ +{ + "results": [ + { + "input": { + "requestId": "row-1" + }, + "persons": [ + { + "name": { + "first": "Dana", + "last": "Reyes" + }, + "phoneNumbers": [ + { + "number": "6025550142", + "type": "Mobile", + "tcpa": false + } + ] + } + ], + "meta": { + "matched": true + } + } + ], + "matchCount": 1, + "requestCount": 1 +} diff --git a/modules/batchVerifyAddress/batchVerifyAddress.communication.iml.json b/modules/batchVerifyAddress/batchVerifyAddress.communication.iml.json new file mode 100644 index 0000000..e25cc6b --- /dev/null +++ b/modules/batchVerifyAddress/batchVerifyAddress.communication.iml.json @@ -0,0 +1,14 @@ +{ + "url": "/v1/address/verify", + "method": "POST", + "body": { + "requests": "{{parameters.addresses}}" + }, + "response": { + "output": { + "addresses": "{{body.results.addresses}}", + "requestCount": "{{body.results.meta.results.requestCount}}", + "normalizedCount": "{{body.results.meta.results.normalizedCount}}" + } + } +} diff --git a/modules/batchVerifyAddress/batchVerifyAddress.interface.iml.json b/modules/batchVerifyAddress/batchVerifyAddress.interface.iml.json new file mode 100644 index 0000000..3fd350e --- /dev/null +++ b/modules/batchVerifyAddress/batchVerifyAddress.interface.iml.json @@ -0,0 +1,56 @@ +[ + { + "name": "addresses", + "type": "array", + "label": "Addresses", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "verified", + "type": "boolean", + "label": "Verified" + } + ] + } + ] + }, + { + "name": "requestCount", + "type": "number", + "label": "Request count" + }, + { + "name": "normalizedCount", + "type": "number", + "label": "Normalized count" + } +] diff --git a/modules/batchVerifyAddress/batchVerifyAddress.mappable-params.iml.json b/modules/batchVerifyAddress/batchVerifyAddress.mappable-params.iml.json new file mode 100644 index 0000000..6ed6b8a --- /dev/null +++ b/modules/batchVerifyAddress/batchVerifyAddress.mappable-params.iml.json @@ -0,0 +1,31 @@ +[ + { + "name": "addresses", + "label": "Addresses", + "required": true, + "help": "One row per address, up to 100.", + "type": "array", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "ZIP Code" + } + ] + } +] diff --git a/modules/batchVerifyAddress/batchVerifyAddress.samples.iml.json b/modules/batchVerifyAddress/batchVerifyAddress.samples.iml.json new file mode 100644 index 0000000..7d11f16 --- /dev/null +++ b/modules/batchVerifyAddress/batchVerifyAddress.samples.iml.json @@ -0,0 +1,16 @@ +{ + "addresses": [ + { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa", + "meta": { + "verified": true + } + } + ], + "requestCount": 1, + "normalizedCount": 1 +} diff --git a/modules/checkDnc/checkDnc.communication.iml.json b/modules/checkDnc/checkDnc.communication.iml.json new file mode 100644 index 0000000..6f0abdc --- /dev/null +++ b/modules/checkDnc/checkDnc.communication.iml.json @@ -0,0 +1,11 @@ +{ + "url": "/v1/phone/dnc", + "method": "POST", + "body": { + "requests": "{{parameters.phoneNumbers}}" + }, + "response": { + "iterate": "{{body.results.phoneNumbers}}", + "output": "{{item}}" + } +} diff --git a/modules/checkDnc/checkDnc.interface.iml.json b/modules/checkDnc/checkDnc.interface.iml.json new file mode 100644 index 0000000..598afc8 --- /dev/null +++ b/modules/checkDnc/checkDnc.interface.iml.json @@ -0,0 +1,29 @@ +[ + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "dnc", + "type": "boolean", + "label": "Dnc" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "error", + "type": "boolean", + "label": "Error" + }, + { + "name": "matched", + "type": "boolean", + "label": "Matched" + } + ] + } +] diff --git a/modules/checkDnc/checkDnc.mappable-params.iml.json b/modules/checkDnc/checkDnc.mappable-params.iml.json new file mode 100644 index 0000000..6ccea73 --- /dev/null +++ b/modules/checkDnc/checkDnc.mappable-params.iml.json @@ -0,0 +1,13 @@ +[ + { + "name": "phoneNumbers", + "type": "array", + "label": "Phone numbers", + "required": true, + "help": "One number per item, for example 4805550123. You are billed per phone number.", + "spec": { + "type": "text", + "label": "Phone number" + } + } +] diff --git a/modules/checkDnc/checkDnc.samples.iml.json b/modules/checkDnc/checkDnc.samples.iml.json new file mode 100644 index 0000000..85c8dfc --- /dev/null +++ b/modules/checkDnc/checkDnc.samples.iml.json @@ -0,0 +1,8 @@ +{ + "number": "6027828692", + "dnc": false, + "meta": { + "error": false, + "matched": true + } +} diff --git a/modules/checkTcpa/checkTcpa.communication.iml.json b/modules/checkTcpa/checkTcpa.communication.iml.json new file mode 100644 index 0000000..20fbb6f --- /dev/null +++ b/modules/checkTcpa/checkTcpa.communication.iml.json @@ -0,0 +1,11 @@ +{ + "url": "/v1/phone/tcpa", + "method": "POST", + "body": { + "requests": "{{parameters.phoneNumbers}}" + }, + "response": { + "iterate": "{{body.results.phoneNumbers}}", + "output": "{{item}}" + } +} diff --git a/modules/checkTcpa/checkTcpa.interface.iml.json b/modules/checkTcpa/checkTcpa.interface.iml.json new file mode 100644 index 0000000..5e64682 --- /dev/null +++ b/modules/checkTcpa/checkTcpa.interface.iml.json @@ -0,0 +1,29 @@ +[ + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "tcpa", + "type": "boolean", + "label": "Tcpa" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "error", + "type": "boolean", + "label": "Error" + }, + { + "name": "matched", + "type": "boolean", + "label": "Matched" + } + ] + } +] diff --git a/modules/checkTcpa/checkTcpa.mappable-params.iml.json b/modules/checkTcpa/checkTcpa.mappable-params.iml.json new file mode 100644 index 0000000..6ccea73 --- /dev/null +++ b/modules/checkTcpa/checkTcpa.mappable-params.iml.json @@ -0,0 +1,13 @@ +[ + { + "name": "phoneNumbers", + "type": "array", + "label": "Phone numbers", + "required": true, + "help": "One number per item, for example 4805550123. You are billed per phone number.", + "spec": { + "type": "text", + "label": "Phone number" + } + } +] diff --git a/modules/checkTcpa/checkTcpa.samples.iml.json b/modules/checkTcpa/checkTcpa.samples.iml.json new file mode 100644 index 0000000..f2de565 --- /dev/null +++ b/modules/checkTcpa/checkTcpa.samples.iml.json @@ -0,0 +1,8 @@ +{ + "number": "2024561111", + "tcpa": false, + "meta": { + "error": false, + "matched": false + } +} diff --git a/modules/createMonitor/createMonitor.communication.iml.json b/modules/createMonitor/createMonitor.communication.iml.json new file mode 100644 index 0000000..b94d27c --- /dev/null +++ b/modules/createMonitor/createMonitor.communication.iml.json @@ -0,0 +1,22 @@ +{ + "url": "/v2/property-subscription", + "method": "POST", + "body": { + "searchCriteria": { + "{{...}}": "{{if(parameters.additionalCriteria, parseJSON(parameters.additionalCriteria), undefined)}}", + "query": "{{ifempty(parameters.query, undefined)}}", + "quickLists": "{{if(length(parameters.quickLists) > 0, parameters.quickLists, undefined)}}", + "orQuickLists": "{{if(length(parameters.orQuickLists) > 0, parameters.orQuickLists, undefined)}}" + }, + "deliveryConfig": { + "type": "{{parameters.deliveryType}}", + "{{...}}": "{{if(parameters.deliveryType == 'kinesis', parameters.kinesis, undefined)}}", + "eventHub": "{{if(parameters.deliveryType == 'event-hub', parameters.eventHub, undefined)}}" + } + }, + "response": { + "output": { + "subscriptionId": "{{body.result.subscriptionId}}" + } + } +} diff --git a/modules/createMonitor/createMonitor.interface.iml.json b/modules/createMonitor/createMonitor.interface.iml.json new file mode 100644 index 0000000..73574f8 --- /dev/null +++ b/modules/createMonitor/createMonitor.interface.iml.json @@ -0,0 +1,7 @@ +[ + { + "name": "subscriptionId", + "type": "text", + "label": "Subscription id" + } +] diff --git a/modules/createMonitor/createMonitor.mappable-params.iml.json b/modules/createMonitor/createMonitor.mappable-params.iml.json new file mode 100644 index 0000000..01ca9f9 --- /dev/null +++ b/modules/createMonitor/createMonitor.mappable-params.iml.json @@ -0,0 +1,425 @@ +[ + { + "name": "query", + "label": "Location Query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\".", + "type": "text" + }, + { + "name": "quickLists", + "label": "Quick Lists (Include)", + "help": "Properties must match ALL selected lists.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "orQuickLists", + "label": "Quick Lists (Match Any)", + "help": "Properties must match AT LEAST ONE selected list.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "additionalCriteria", + "label": "Additional Criteria (JSON)", + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored. Optional JSON object with extra filters, for example {\"general\":{\"propertyTypeCategory\":{\"equals\":\"Single Family\"}}}. Fields you fill in above take priority.", + "type": "text" + }, + { + "name": "deliveryType", + "label": "Delivery Type", + "required": true, + "type": "select", + "options": [ + { + "label": "AWS Kinesis", + "value": "kinesis" + }, + { + "label": "Azure Event Hub", + "value": "event-hub" + } + ], + "default": "kinesis" + }, + { + "name": "kinesis", + "type": "collection", + "label": "AWS Kinesis settings", + "help": "Used when Delivery type is \"kinesis\".", + "spec": [ + { + "name": "streamName", + "label": "Kinesis Stream Name", + "type": "text" + }, + { + "name": "region", + "label": "Kinesis AWS Region", + "help": "For example us-west-2.", + "type": "text" + }, + { + "name": "iamAccessKeyId", + "label": "Kinesis IAM Access Key ID", + "type": "text" + }, + { + "name": "iamSecretAccessKey", + "label": "Kinesis IAM Secret Access Key", + "type": "password" + } + ] + }, + { + "name": "eventHub", + "type": "collection", + "label": "Azure Event Hub settings", + "help": "Used when Delivery type is \"event-hub\".", + "spec": [ + { + "name": "eventHubName", + "label": "Event Hub Name", + "type": "text" + }, + { + "name": "connectionString", + "label": "Event Hub Connection String", + "help": "Use this, or the namespace and service principal fields below.", + "type": "password" + }, + { + "name": "fullyQualifiedNamespace", + "label": "Event Hub Fully Qualified Namespace", + "type": "text" + }, + { + "name": "clientId", + "label": "Event Hub Client ID", + "type": "text" + }, + { + "name": "tenantId", + "label": "Event Hub Tenant ID", + "type": "text" + }, + { + "name": "clientSecret", + "label": "Event Hub Client Secret", + "type": "password" + } + ] + } +] diff --git a/modules/createMonitor/createMonitor.samples.iml.json b/modules/createMonitor/createMonitor.samples.iml.json new file mode 100644 index 0000000..88fb2fd --- /dev/null +++ b/modules/createMonitor/createMonitor.samples.iml.json @@ -0,0 +1,3 @@ +{ + "subscriptionId": "FXeLSlemePGGhvt" +} diff --git a/modules/createSearchSession/createSearchSession.communication.iml.json b/modules/createSearchSession/createSearchSession.communication.iml.json new file mode 100644 index 0000000..25d3e03 --- /dev/null +++ b/modules/createSearchSession/createSearchSession.communication.iml.json @@ -0,0 +1,26 @@ +{ + "url": "/v1/property/search", + "method": "POST", + "body": { + "searchCriteria": { + "{{...}}": "{{if(parameters.additionalCriteria, parseJSON(parameters.additionalCriteria), undefined)}}", + "query": "{{ifempty(parameters.query, undefined)}}", + "quickLists": "{{if(length(parameters.quickLists) > 0, parameters.quickLists, undefined)}}", + "orQuickLists": "{{if(length(parameters.orQuickLists) > 0, parameters.orQuickLists, undefined)}}" + }, + "options": { + "searchSession": { + "id": "{{parameters.sessionId}}" + }, + "take": "{{ifempty(parameters.take, 1)}}", + "useCursorPagination": true + } + }, + "response": { + "output": { + "properties": "{{body.results.properties}}", + "session": "{{body.results.searchSessionMeta}}", + "searchMeta": "{{body.results.meta.results}}" + } + } +} diff --git a/modules/createSearchSession/createSearchSession.interface.iml.json b/modules/createSearchSession/createSearchSession.interface.iml.json new file mode 100644 index 0000000..0f012dd --- /dev/null +++ b/modules/createSearchSession/createSearchSession.interface.iml.json @@ -0,0 +1,104 @@ +[ + { + "name": "properties", + "type": "array", + "label": "Properties", + "spec": [ + { + "name": "_id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "county", + "type": "text", + "label": "County" + } + ] + }, + { + "name": "general", + "type": "collection", + "label": "General", + "spec": [ + { + "name": "propertyTypeCategory", + "type": "text", + "label": "Property type category" + } + ] + }, + { + "name": "quickLists", + "type": "collection", + "label": "Quick lists", + "spec": [ + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + } + ] + } + ] + }, + { + "name": "session", + "type": "collection", + "label": "Session", + "spec": [ + { + "name": "totalDelivered", + "type": "number", + "label": "Total delivered" + }, + { + "name": "isNewSession", + "type": "boolean", + "label": "Is new session" + } + ] + }, + { + "name": "searchMeta", + "type": "collection", + "label": "Search meta", + "spec": [ + { + "name": "resultCount", + "type": "number", + "label": "Result count" + }, + { + "name": "resultsFound", + "type": "number", + "label": "Results found" + } + ] + } +] diff --git a/modules/createSearchSession/createSearchSession.mappable-params.iml.json b/modules/createSearchSession/createSearchSession.mappable-params.iml.json new file mode 100644 index 0000000..9c35fa6 --- /dev/null +++ b/modules/createSearchSession/createSearchSession.mappable-params.iml.json @@ -0,0 +1,361 @@ +[ + { + "name": "sessionId", + "label": "Session ID", + "required": true, + "help": "Your own name for the session: letters, numbers, dash or underscore, up to 128 characters. Reusing the same ID continues the session, so you are never billed twice for the same property. A team can hold 100 sessions.", + "type": "text", + "validate": { + "pattern": "^[A-Za-z0-9_-]{1,128}$" + } + }, + { + "name": "query", + "label": "Location Query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\".", + "type": "text" + }, + { + "name": "quickLists", + "label": "Quick Lists (Include)", + "help": "Properties must match ALL selected lists.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "orQuickLists", + "label": "Quick Lists (Match Any)", + "help": "Properties must match AT LEAST ONE selected list.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "additionalCriteria", + "label": "Additional Criteria (JSON)", + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored. Optional JSON object with extra filters, for example {\"general\":{\"propertyTypeCategory\":{\"equals\":\"Single Family\"}}}. Fields you fill in above take priority.", + "type": "text" + }, + { + "name": "take", + "label": "Max Results", + "help": "Whole number from 1 to 100. Default 1. You are billed per record delivered.", + "type": "integer", + "default": 1, + "validate": { + "min": 1, + "max": 100 + } + } +] diff --git a/modules/createSearchSession/createSearchSession.samples.iml.json b/modules/createSearchSession/createSearchSession.samples.iml.json new file mode 100644 index 0000000..4a9f428 --- /dev/null +++ b/modules/createSearchSession/createSearchSession.samples.iml.json @@ -0,0 +1,28 @@ +{ + "properties": [ + { + "_id": "09201d3bd3134f9380704014781fb4c0", + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa" + }, + "general": { + "propertyTypeCategory": "Single Family" + }, + "quickLists": { + "vacant": false + } + } + ], + "session": { + "totalDelivered": 1, + "isNewSession": true + }, + "searchMeta": { + "resultCount": 1, + "resultsFound": 1 + } +} diff --git a/modules/deleteMonitor/deleteMonitor.communication.iml.json b/modules/deleteMonitor/deleteMonitor.communication.iml.json new file mode 100644 index 0000000..ce3da9c --- /dev/null +++ b/modules/deleteMonitor/deleteMonitor.communication.iml.json @@ -0,0 +1,10 @@ +{ + "url": "/v2/property-subscription/{{replace(replace(parameters.id, '/', '_'), '.', '_')}}", + "method": "DELETE", + "response": { + "output": { + "deleted": true, + "id": "{{parameters.id}}" + } + } +} diff --git a/modules/deleteMonitor/deleteMonitor.interface.iml.json b/modules/deleteMonitor/deleteMonitor.interface.iml.json new file mode 100644 index 0000000..257b776 --- /dev/null +++ b/modules/deleteMonitor/deleteMonitor.interface.iml.json @@ -0,0 +1,12 @@ +[ + { + "name": "deleted", + "type": "boolean", + "label": "Deleted" + }, + { + "name": "id", + "type": "text", + "label": "Id" + } +] diff --git a/modules/deleteMonitor/deleteMonitor.mappable-params.iml.json b/modules/deleteMonitor/deleteMonitor.mappable-params.iml.json new file mode 100644 index 0000000..d41d60f --- /dev/null +++ b/modules/deleteMonitor/deleteMonitor.mappable-params.iml.json @@ -0,0 +1,10 @@ +[ + { + "name": "id", + "label": "Monitor ID", + "required": true, + "help": "The ID of the monitor to delete.", + "type": "select", + "options": "rpc://listMonitors" + } +] diff --git a/modules/deleteMonitor/deleteMonitor.samples.iml.json b/modules/deleteMonitor/deleteMonitor.samples.iml.json new file mode 100644 index 0000000..f857dec --- /dev/null +++ b/modules/deleteMonitor/deleteMonitor.samples.iml.json @@ -0,0 +1,4 @@ +{ + "deleted": true, + "id": "FXeLSlemePGGhvt" +} diff --git a/modules/deleteSearchSession/deleteSearchSession.communication.iml.json b/modules/deleteSearchSession/deleteSearchSession.communication.iml.json new file mode 100644 index 0000000..bc2f6dd --- /dev/null +++ b/modules/deleteSearchSession/deleteSearchSession.communication.iml.json @@ -0,0 +1,10 @@ +{ + "url": "/v2/property/search-sessions/{{replace(replace(parameters.id, '/', '_'), '.', '_')}}", + "method": "DELETE", + "response": { + "output": { + "deleted": "{{body.results.deleted}}", + "id": "{{body.results.id}}" + } + } +} diff --git a/modules/deleteSearchSession/deleteSearchSession.interface.iml.json b/modules/deleteSearchSession/deleteSearchSession.interface.iml.json new file mode 100644 index 0000000..257b776 --- /dev/null +++ b/modules/deleteSearchSession/deleteSearchSession.interface.iml.json @@ -0,0 +1,12 @@ +[ + { + "name": "deleted", + "type": "boolean", + "label": "Deleted" + }, + { + "name": "id", + "type": "text", + "label": "Id" + } +] diff --git a/modules/deleteSearchSession/deleteSearchSession.mappable-params.iml.json b/modules/deleteSearchSession/deleteSearchSession.mappable-params.iml.json new file mode 100644 index 0000000..e0bac56 --- /dev/null +++ b/modules/deleteSearchSession/deleteSearchSession.mappable-params.iml.json @@ -0,0 +1,10 @@ +[ + { + "name": "id", + "label": "Session ID", + "required": true, + "help": "The ID of the session to delete.", + "type": "select", + "options": "rpc://listSearchSessions" + } +] diff --git a/modules/deleteSearchSession/deleteSearchSession.samples.iml.json b/modules/deleteSearchSession/deleteSearchSession.samples.iml.json new file mode 100644 index 0000000..fc393ff --- /dev/null +++ b/modules/deleteSearchSession/deleteSearchSession.samples.iml.json @@ -0,0 +1,4 @@ +{ + "deleted": true, + "id": "monthly-leads-az-85020" +} diff --git a/modules/geocodeAddress/geocodeAddress.communication.iml.json b/modules/geocodeAddress/geocodeAddress.communication.iml.json new file mode 100644 index 0000000..10cd8c4 --- /dev/null +++ b/modules/geocodeAddress/geocodeAddress.communication.iml.json @@ -0,0 +1,16 @@ +{ + "url": "/v1/address/geocode", + "method": "POST", + "body": { + "requests": [ + { + "address": "{{parameters.address}}", + "requestId": "{{ifempty(parameters.requestId, undefined)}}" + } + ] + }, + "response": { + "iterate": "{{body.result.addresses}}", + "output": "{{item}}" + } +} diff --git a/modules/geocodeAddress/geocodeAddress.interface.iml.json b/modules/geocodeAddress/geocodeAddress.interface.iml.json new file mode 100644 index 0000000..9ec63b0 --- /dev/null +++ b/modules/geocodeAddress/geocodeAddress.interface.iml.json @@ -0,0 +1,64 @@ +[ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "fullAddress", + "type": "text", + "label": "Full address" + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "geoStatus", + "type": "text", + "label": "Geo status" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "error", + "type": "boolean", + "label": "Error" + }, + { + "name": "geocoded", + "type": "boolean", + "label": "Geocoded" + } + ] + } +] diff --git a/modules/geocodeAddress/geocodeAddress.mappable-params.iml.json b/modules/geocodeAddress/geocodeAddress.mappable-params.iml.json new file mode 100644 index 0000000..d581726 --- /dev/null +++ b/modules/geocodeAddress/geocodeAddress.mappable-params.iml.json @@ -0,0 +1,14 @@ +[ + { + "name": "address", + "label": "Address", + "required": true, + "help": "One full address, for example 2800 N 24th St, Phoenix, AZ 85008.", + "type": "text" + }, + { + "name": "requestId", + "label": "Your Reference ID", + "type": "text" + } +] diff --git a/modules/geocodeAddress/geocodeAddress.samples.iml.json b/modules/geocodeAddress/geocodeAddress.samples.iml.json new file mode 100644 index 0000000..1b8498d --- /dev/null +++ b/modules/geocodeAddress/geocodeAddress.samples.iml.json @@ -0,0 +1,15 @@ +{ + "street": "2800 N 24th St", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85008", + "fullAddress": "2800 N 24th St, Phoenix, Maricopa, AZ, 85008", + "latitude": 33.478332, + "longitude": -112.030474, + "geoStatus": "Rooftop", + "meta": { + "error": false, + "geocoded": true + } +} diff --git a/modules/getMonitor/getMonitor.communication.iml.json b/modules/getMonitor/getMonitor.communication.iml.json new file mode 100644 index 0000000..ac0a732 --- /dev/null +++ b/modules/getMonitor/getMonitor.communication.iml.json @@ -0,0 +1,7 @@ +{ + "url": "/v2/property-subscription/{{replace(replace(parameters.id, '/', '_'), '.', '_')}}", + "method": "GET", + "response": { + "output": "{{body.result}}" + } +} diff --git a/modules/getMonitor/getMonitor.interface.iml.json b/modules/getMonitor/getMonitor.interface.iml.json new file mode 100644 index 0000000..880e2d2 --- /dev/null +++ b/modules/getMonitor/getMonitor.interface.iml.json @@ -0,0 +1,49 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "searchCriteria", + "type": "collection", + "label": "Search criteria", + "spec": [ + { + "name": "query", + "type": "text", + "label": "Query" + }, + { + "name": "orQuickLists", + "type": "array", + "label": "Or quick lists", + "spec": { + "type": "text" + } + } + ] + }, + { + "name": "deliveryConfig", + "type": "collection", + "label": "Delivery config", + "spec": [ + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "streamName", + "type": "text", + "label": "Stream name" + }, + { + "name": "region", + "type": "text", + "label": "Region" + } + ] + } +] diff --git a/modules/getMonitor/getMonitor.mappable-params.iml.json b/modules/getMonitor/getMonitor.mappable-params.iml.json new file mode 100644 index 0000000..631f8c8 --- /dev/null +++ b/modules/getMonitor/getMonitor.mappable-params.iml.json @@ -0,0 +1,10 @@ +[ + { + "name": "id", + "label": "Monitor ID", + "required": true, + "help": "The ID of the monitor.", + "type": "select", + "options": "rpc://listMonitors" + } +] diff --git a/modules/getMonitor/getMonitor.samples.iml.json b/modules/getMonitor/getMonitor.samples.iml.json new file mode 100644 index 0000000..6bac24d --- /dev/null +++ b/modules/getMonitor/getMonitor.samples.iml.json @@ -0,0 +1,14 @@ +{ + "id": "cjui0E2T1LKxWOD", + "searchCriteria": { + "query": "Phoenix, AZ", + "orQuickLists": [ + "on-market" + ] + }, + "deliveryConfig": { + "type": "kinesis", + "streamName": "my-stream", + "region": "us-west-2" + } +} diff --git a/modules/getPermits/getPermits.communication.iml.json b/modules/getPermits/getPermits.communication.iml.json new file mode 100644 index 0000000..baa1966 --- /dev/null +++ b/modules/getPermits/getPermits.communication.iml.json @@ -0,0 +1,29 @@ +{ + "url": "/v2/property/get-property-permits", + "method": "POST", + "body": { + "request": { + "address": "{{ifempty(parameters.address, undefined)}}", + "status": "{{ifempty(parameters.status, undefined)}}", + "type": "{{ifempty(parameters.type, undefined)}}", + "description": "{{ifempty(parameters.description, undefined)}}", + "allTags": "{{ifempty(parameters.allTags, undefined)}}", + "issueDate": "{{ifempty(parameters.issueDate, undefined)}}", + "jobValue": "{{ifempty(parameters.jobValue, undefined)}}", + "propertyId": "{{ifempty(parameters.propertyId, undefined)}}", + "apn": "{{ifempty(parameters.apn, undefined)}}", + "countyFipsCode": "{{ifempty(parameters.countyFipsCode, undefined)}}" + }, + "options": { + "take": "{{ifempty(parameters.take, 25)}}" + } + }, + "response": { + "iterate": "{{body.result.permits}}", + "output": { + "{{...}}": "{{item}}", + "property": "{{body.result.property}}", + "searchMeta": "{{body.result.meta.results}}" + } + } +} diff --git a/modules/getPermits/getPermits.interface.iml.json b/modules/getPermits/getPermits.interface.iml.json new file mode 100644 index 0000000..0d65601 --- /dev/null +++ b/modules/getPermits/getPermits.interface.iml.json @@ -0,0 +1,106 @@ +[ + { + "name": "permitNumber", + "type": "text", + "label": "Permit number" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "subtype", + "type": "text", + "label": "Subtype" + }, + { + "name": "status", + "type": "text", + "label": "Status" + }, + { + "name": "description", + "type": "text", + "label": "Description" + }, + { + "name": "issueDate", + "type": "text", + "label": "Issue date" + }, + { + "name": "finalDate", + "type": "text", + "label": "Final date" + }, + { + "name": "jobValue", + "type": "number", + "label": "Job value" + }, + { + "name": "jurisdiction", + "type": "text", + "label": "Jurisdiction" + }, + { + "name": "allTags", + "type": "array", + "label": "All tags", + "spec": { + "type": "text" + } + }, + { + "name": "property", + "type": "collection", + "label": "Property", + "spec": [ + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + } + ] + } + ] + }, + { + "name": "searchMeta", + "type": "collection", + "label": "Search meta", + "spec": [ + { + "name": "resultCount", + "type": "number", + "label": "Result count" + }, + { + "name": "resultsFound", + "type": "number", + "label": "Results found" + } + ] + } +] diff --git a/modules/getPermits/getPermits.mappable-params.iml.json b/modules/getPermits/getPermits.mappable-params.iml.json new file mode 100644 index 0000000..ea0ad98 --- /dev/null +++ b/modules/getPermits/getPermits.mappable-params.iml.json @@ -0,0 +1,261 @@ +[ + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "label": "Street", + "help": "Street address with house number, for example 2800 N 24th St.", + "type": "text" + }, + { + "name": "city", + "label": "City", + "type": "text" + }, + { + "name": "state", + "label": "State", + "help": "Two-letter state code, for example AZ.", + "type": "text" + }, + { + "name": "zip", + "label": "ZIP Code", + "type": "text" + } + ] + }, + { + "name": "propertyId", + "label": "BatchData Property ID", + "type": "text" + }, + { + "name": "apn", + "label": "APN", + "type": "text" + }, + { + "name": "countyFipsCode", + "label": "County FIPS Code", + "help": "Use with APN.", + "type": "text" + }, + { + "name": "status", + "type": "collection", + "label": "Status", + "spec": [ + { + "name": "equals", + "label": "Permit Status", + "type": "select", + "options": [ + { + "label": "active", + "value": "active" + }, + { + "label": "final", + "value": "final" + }, + { + "label": "in_review", + "value": "in_review" + }, + { + "label": "inactive", + "value": "inactive" + } + ] + } + ] + }, + { + "name": "type", + "type": "collection", + "label": "Type", + "spec": [ + { + "name": "contains", + "label": "Permit Type Contains", + "type": "text" + } + ] + }, + { + "name": "description", + "type": "collection", + "label": "Description", + "spec": [ + { + "name": "contains", + "label": "Description Contains", + "type": "text" + } + ] + }, + { + "name": "allTags", + "type": "collection", + "label": "All Tags", + "spec": [ + { + "name": "inList", + "label": "Permit Tags (Match Any)", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "addition", + "value": "addition" + }, + { + "label": "adu", + "value": "adu" + }, + { + "label": "bathroom", + "value": "bathroom" + }, + { + "label": "battery", + "value": "battery" + }, + { + "label": "demolition", + "value": "demolition" + }, + { + "label": "electrical", + "value": "electrical" + }, + { + "label": "electricMeter", + "value": "electricMeter" + }, + { + "label": "evCharger", + "value": "evCharger" + }, + { + "label": "fireSprinkler", + "value": "fireSprinkler" + }, + { + "label": "gas", + "value": "gas" + }, + { + "label": "generator", + "value": "generator" + }, + { + "label": "grading", + "value": "grading" + }, + { + "label": "heatPump", + "value": "heatPump" + }, + { + "label": "hvac", + "value": "hvac" + }, + { + "label": "inspectionPassed", + "value": "inspectionPassed" + }, + { + "label": "kitchen", + "value": "kitchen" + }, + { + "label": "newConstruction", + "value": "newConstruction" + }, + { + "label": "plumbing", + "value": "plumbing" + }, + { + "label": "poolAndHotTub", + "value": "poolAndHotTub" + }, + { + "label": "remodel", + "value": "remodel" + }, + { + "label": "roofing", + "value": "roofing" + }, + { + "label": "solar", + "value": "solar" + }, + { + "label": "waterHeater", + "value": "waterHeater" + }, + { + "label": "windowDoor", + "value": "windowDoor" + } + ] + } + } + ] + }, + { + "name": "issueDate", + "type": "collection", + "label": "Issue Date", + "spec": [ + { + "name": "minDate", + "label": "Issued From (YYYY-MM-DD)", + "help": "Format YYYY-MM-DD.", + "type": "text" + }, + { + "name": "maxDate", + "label": "Issued To (YYYY-MM-DD)", + "help": "Format YYYY-MM-DD.", + "type": "text" + } + ] + }, + { + "name": "jobValue", + "type": "collection", + "label": "Job Value", + "spec": [ + { + "name": "min", + "label": "Job Value (Min)", + "type": "number" + }, + { + "name": "max", + "label": "Job Value (Max)", + "type": "number" + } + ] + }, + { + "name": "take", + "label": "Max Results", + "help": "Whole number from 1 to 100. Default 25.", + "type": "integer", + "default": 25, + "validate": { + "min": 1, + "max": 100 + } + } +] diff --git a/modules/getPermits/getPermits.samples.iml.json b/modules/getPermits/getPermits.samples.iml.json new file mode 100644 index 0000000..126c5ad --- /dev/null +++ b/modules/getPermits/getPermits.samples.iml.json @@ -0,0 +1,26 @@ +{ + "permitNumber": "BP-2026-004211", + "type": "Building", + "subtype": "Residential", + "status": "final", + "description": "Re-roof, composition shingle", + "issueDate": "2026-02-03", + "finalDate": "2026-02-20", + "jobValue": 14500, + "jurisdiction": "Phoenix", + "allTags": [ + "roofing" + ], + "property": { + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008" + } + }, + "searchMeta": { + "resultCount": 1, + "resultsFound": 1 + } +} diff --git a/modules/groups.json b/modules/groups.json new file mode 100644 index 0000000..7f8a29b --- /dev/null +++ b/modules/groups.json @@ -0,0 +1,77 @@ +[ + { + "label": "Properties", + "modules": [ + "searchProperties", + "lookupProperty", + "batchLookupProperty", + "getPermits" + ] + }, + { + "label": "Owner contact", + "modules": [ + "skipTraceV1", + "skipTraceV3", + "reverseSkipTrace", + "batchSkipTrace" + ] + }, + { + "label": "Addresses", + "modules": [ + "verifyAddress", + "autocompleteAddress", + "geocodeAddress", + "reverseGeocode", + "batchVerifyAddress" + ] + }, + { + "label": "Phone numbers", + "modules": [ + "verifyPhones", + "checkDnc", + "checkTcpa" + ] + }, + { + "label": "Investors", + "modules": [ + "investorBuybox", + "investorProperties" + ] + }, + { + "label": "Search sessions", + "modules": [ + "createSearchSession", + "listSearchSessions", + "deleteSearchSession" + ] + }, + { + "label": "Property monitors", + "modules": [ + "createMonitor", + "listMonitors", + "getMonitor", + "deleteMonitor" + ] + }, + { + "label": "Triggers", + "modules": [ + "watchSearchSessions", + "watchSessionProperties", + "watchMonitorEvents", + "watchMonitorEventsWithProperty" + ] + }, + { + "label": "Other", + "modules": [ + "makeAnApiCall" + ] + } +] diff --git a/modules/investorBuybox/investorBuybox.communication.iml.json b/modules/investorBuybox/investorBuybox.communication.iml.json new file mode 100644 index 0000000..efdd0a1 --- /dev/null +++ b/modules/investorBuybox/investorBuybox.communication.iml.json @@ -0,0 +1,28 @@ +{ + "url": "/v2/investor/buybox", + "method": "POST", + "body": { + "searchCriteria": { + "investor": "{{ifempty(parameters.investor, undefined)}}", + "intel": "{{ifempty(parameters.intel, undefined)}}", + "{{...}}": "{{if(parameters.additionalCriteria, parseJSON(parameters.additionalCriteria), undefined)}}", + "compAddress": { + "street": "{{parameters.street}}", + "city": "{{ifempty(parameters.city, undefined)}}", + "state": "{{ifempty(parameters.state, undefined)}}", + "zip": "{{ifempty(parameters.zip, undefined)}}" + } + }, + "options": { + "take": "{{ifempty(parameters.take, 25)}}" + } + }, + "response": { + "iterate": "{{body.result.investors}}", + "output": { + "{{...}}": "{{item}}", + "searchMeta": "{{body.result.meta.results}}", + "warnings": "{{body.result.warnings}}" + } + } +} diff --git a/modules/investorBuybox/investorBuybox.interface.iml.json b/modules/investorBuybox/investorBuybox.interface.iml.json new file mode 100644 index 0000000..d6df93f --- /dev/null +++ b/modules/investorBuybox/investorBuybox.interface.iml.json @@ -0,0 +1,111 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "name", + "type": "text", + "label": "Name" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "classification", + "type": "array", + "label": "Classification", + "spec": { + "type": "text" + } + }, + { + "name": "currentHoldings", + "type": "collection", + "label": "Current holdings", + "spec": [ + { + "name": "totalPropertyCount", + "type": "number", + "label": "Total property count" + } + ] + }, + { + "name": "acquisitions", + "type": "collection", + "label": "Acquisitions", + "spec": [ + { + "name": "totalAcquisitionCount", + "type": "number", + "label": "Total acquisition count" + } + ] + }, + { + "name": "mailingAddresses", + "type": "array", + "label": "Mailing addresses", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "propertyCount", + "type": "number", + "label": "Property count" + } + ] + }, + { + "name": "matchedAsCurrentOwner", + "type": "boolean", + "label": "Matched as current owner" + }, + { + "name": "searchMeta", + "type": "collection", + "label": "Search meta", + "spec": [ + { + "name": "resultCount", + "type": "number", + "label": "Result count" + }, + { + "name": "resultsFound", + "type": "number", + "label": "Results found" + } + ] + }, + { + "name": "warnings", + "type": "array", + "label": "Warnings", + "spec": { + "type": "text" + } + } +] diff --git a/modules/investorBuybox/investorBuybox.mappable-params.iml.json b/modules/investorBuybox/investorBuybox.mappable-params.iml.json new file mode 100644 index 0000000..e01e228 --- /dev/null +++ b/modules/investorBuybox/investorBuybox.mappable-params.iml.json @@ -0,0 +1,109 @@ +[ + { + "name": "street", + "label": "Subject Property Street", + "required": true, + "help": "Street address with house number, for example 2800 N 24th St.", + "type": "text" + }, + { + "name": "city", + "label": "Subject Property City", + "type": "text" + }, + { + "name": "state", + "label": "Subject Property State", + "help": "Two-letter state code, for example AZ.", + "type": "text" + }, + { + "name": "zip", + "label": "Subject Property ZIP Code", + "type": "text" + }, + { + "name": "investor", + "type": "collection", + "label": "Investor", + "spec": [ + { + "name": "classification", + "type": "collection", + "label": "Classification", + "spec": [ + { + "name": "inList", + "label": "Investor Types", + "help": "For example FLIPPER, LANDLORD, CASH BUYER. Replaces the default (FLIPPER and LANDLORD).", + "type": "array", + "spec": { + "type": "text" + } + } + ] + }, + { + "name": "ownership", + "label": "Ownership", + "help": "Which owners of a comparable property qualify. Default any.", + "type": "select", + "options": [ + { + "label": "current", + "value": "current" + }, + { + "label": "previous", + "value": "previous" + }, + { + "label": "any", + "value": "any" + } + ] + } + ] + }, + { + "name": "intel", + "type": "collection", + "label": "Intel", + "spec": [ + { + "name": "lastSoldDate", + "type": "collection", + "label": "Last Sold Date", + "spec": [ + { + "name": "withinLastMonths", + "label": "Comparable Sales Within (Months)", + "help": "From 1 to 60. Default 12.", + "type": "integer", + "validate": { + "min": 1, + "max": 60 + } + } + ] + } + ] + }, + { + "name": "additionalCriteria", + "label": "Additional Criteria (JSON)", + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored. Optional JSON object with extra filters, for example {\"general\":{\"propertyTypeCategory\":{\"equals\":\"Single Family\"}}}. Fields you fill in above take priority.", + "type": "text" + }, + { + "name": "take", + "label": "Max Results", + "help": "Whole number from 1 to 100. Default 25.", + "type": "integer", + "default": 25, + "validate": { + "min": 1, + "max": 100 + } + } +] diff --git a/modules/investorBuybox/investorBuybox.samples.iml.json b/modules/investorBuybox/investorBuybox.samples.iml.json new file mode 100644 index 0000000..6c90588 --- /dev/null +++ b/modules/investorBuybox/investorBuybox.samples.iml.json @@ -0,0 +1,29 @@ +{ + "id": "inv_a1b2c3d4", + "name": "Evergreen Holdings LLC", + "type": "Company Owned", + "classification": [ + "LANDLORD" + ], + "currentHoldings": { + "totalPropertyCount": 48 + }, + "acquisitions": { + "totalAcquisitionCount": 12 + }, + "mailingAddresses": [ + { + "street": "1200 W Camelback Rd", + "city": "Phoenix", + "state": "AZ", + "zip": "85013", + "propertyCount": 48 + } + ], + "matchedAsCurrentOwner": true, + "searchMeta": { + "resultCount": 25, + "resultsFound": 137 + }, + "warnings": [] +} diff --git a/modules/investorProperties/investorProperties.communication.iml.json b/modules/investorProperties/investorProperties.communication.iml.json new file mode 100644 index 0000000..74863b2 --- /dev/null +++ b/modules/investorProperties/investorProperties.communication.iml.json @@ -0,0 +1,23 @@ +{ + "url": "/v2/investor/properties", + "method": "POST", + "body": { + "searchCriteria": { + "investorId": "{{parameters.investorId}}", + "ownership": "{{ifempty(parameters.ownership, undefined)}}", + "includeChildEntities": "{{if(parameters.includeChildEntities == true, true, undefined)}}" + }, + "options": { + "take": "{{ifempty(parameters.take, 25)}}", + "pageCursor": "{{ifempty(parameters.pageCursor, undefined)}}" + } + }, + "response": { + "iterate": "{{body.result.properties}}", + "output": { + "{{...}}": "{{item}}", + "resultsFound": "{{body.result.resultsFound}}", + "nextPageCursor": "{{body.result.nextPageCursor}}" + } + } +} diff --git a/modules/investorProperties/investorProperties.interface.iml.json b/modules/investorProperties/investorProperties.interface.iml.json new file mode 100644 index 0000000..f82d834 --- /dev/null +++ b/modules/investorProperties/investorProperties.interface.iml.json @@ -0,0 +1,81 @@ +[ + { + "name": "propertyId", + "type": "text", + "label": "Property id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + } + ] + }, + { + "name": "fips", + "type": "text", + "label": "Fips" + }, + { + "name": "apn", + "type": "text", + "label": "Apn" + }, + { + "name": "ownership", + "type": "collection", + "label": "Ownership", + "spec": [ + { + "name": "isCurrentOwner", + "type": "boolean", + "label": "Is current owner" + }, + { + "name": "isPreviousOwner", + "type": "boolean", + "label": "Is previous owner" + } + ] + }, + { + "name": "resultsFound", + "type": "number", + "label": "Results found" + }, + { + "name": "nextPageCursor", + "type": "text", + "label": "Next page cursor" + } +] diff --git a/modules/investorProperties/investorProperties.mappable-params.iml.json b/modules/investorProperties/investorProperties.mappable-params.iml.json new file mode 100644 index 0000000..f2ae100 --- /dev/null +++ b/modules/investorProperties/investorProperties.mappable-params.iml.json @@ -0,0 +1,50 @@ +[ + { + "name": "investorId", + "label": "Investor ID", + "required": true, + "type": "text" + }, + { + "name": "ownership", + "label": "Ownership", + "help": "Default current. \"any\" can return many more rows.", + "type": "select", + "options": [ + { + "label": "current", + "value": "current" + }, + { + "label": "previous", + "value": "previous" + }, + { + "label": "any", + "value": "any" + } + ] + }, + { + "name": "includeChildEntities", + "label": "Include Child Entities", + "type": "boolean" + }, + { + "name": "take", + "label": "Max Results", + "help": "Whole number from 1 to 100. Default 25.", + "type": "integer", + "default": 25, + "validate": { + "min": 1, + "max": 100 + } + }, + { + "name": "pageCursor", + "label": "Page Cursor", + "help": "Optional. The nextPageCursor from the previous page, used with the same criteria.", + "type": "text" + } +] diff --git a/modules/investorProperties/investorProperties.samples.iml.json b/modules/investorProperties/investorProperties.samples.iml.json new file mode 100644 index 0000000..c5c4815 --- /dev/null +++ b/modules/investorProperties/investorProperties.samples.iml.json @@ -0,0 +1,19 @@ +{ + "propertyId": "P-85053-0a1b2c", + "address": { + "street": "3802 W Evans Dr", + "city": "Phoenix", + "state": "AZ", + "zip": "85053", + "latitude": 33.6203, + "longitude": -112.1394 + }, + "fips": "04013", + "apn": "207-14-118", + "ownership": { + "isCurrentOwner": true, + "isPreviousOwner": false + }, + "resultsFound": 48, + "nextPageCursor": "eyJwYWdlIjoyfQ" +} diff --git a/modules/listMonitors/listMonitors.communication.iml.json b/modules/listMonitors/listMonitors.communication.iml.json new file mode 100644 index 0000000..34b3f91 --- /dev/null +++ b/modules/listMonitors/listMonitors.communication.iml.json @@ -0,0 +1,8 @@ +{ + "url": "/v2/property-subscription", + "method": "GET", + "response": { + "iterate": "{{body.results.subscriptions}}", + "output": "{{item}}" + } +} diff --git a/modules/listMonitors/listMonitors.interface.iml.json b/modules/listMonitors/listMonitors.interface.iml.json new file mode 100644 index 0000000..880e2d2 --- /dev/null +++ b/modules/listMonitors/listMonitors.interface.iml.json @@ -0,0 +1,49 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "searchCriteria", + "type": "collection", + "label": "Search criteria", + "spec": [ + { + "name": "query", + "type": "text", + "label": "Query" + }, + { + "name": "orQuickLists", + "type": "array", + "label": "Or quick lists", + "spec": { + "type": "text" + } + } + ] + }, + { + "name": "deliveryConfig", + "type": "collection", + "label": "Delivery config", + "spec": [ + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "streamName", + "type": "text", + "label": "Stream name" + }, + { + "name": "region", + "type": "text", + "label": "Region" + } + ] + } +] diff --git a/modules/listMonitors/listMonitors.mappable-params.iml.json b/modules/listMonitors/listMonitors.mappable-params.iml.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/modules/listMonitors/listMonitors.mappable-params.iml.json @@ -0,0 +1 @@ +[] diff --git a/modules/listMonitors/listMonitors.samples.iml.json b/modules/listMonitors/listMonitors.samples.iml.json new file mode 100644 index 0000000..6bac24d --- /dev/null +++ b/modules/listMonitors/listMonitors.samples.iml.json @@ -0,0 +1,14 @@ +{ + "id": "cjui0E2T1LKxWOD", + "searchCriteria": { + "query": "Phoenix, AZ", + "orQuickLists": [ + "on-market" + ] + }, + "deliveryConfig": { + "type": "kinesis", + "streamName": "my-stream", + "region": "us-west-2" + } +} diff --git a/modules/listSearchSessions/listSearchSessions.communication.iml.json b/modules/listSearchSessions/listSearchSessions.communication.iml.json new file mode 100644 index 0000000..4b7d2c2 --- /dev/null +++ b/modules/listSearchSessions/listSearchSessions.communication.iml.json @@ -0,0 +1,12 @@ +{ + "url": "/v2/property/search-sessions", + "method": "GET", + "qs": { + "take": "{{ifempty(parameters.take, 50)}}", + "skip": "{{ifempty(parameters.skip, undefined)}}" + }, + "response": { + "iterate": "{{body.results.sessions}}", + "output": "{{item}}" + } +} diff --git a/modules/listSearchSessions/listSearchSessions.interface.iml.json b/modules/listSearchSessions/listSearchSessions.interface.iml.json new file mode 100644 index 0000000..ced37e9 --- /dev/null +++ b/modules/listSearchSessions/listSearchSessions.interface.iml.json @@ -0,0 +1,22 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "totalDelivered", + "type": "number", + "label": "Total delivered" + }, + { + "name": "lastRequestAt", + "type": "text", + "label": "Last request at" + }, + { + "name": "createdAt", + "type": "text", + "label": "Created at" + } +] diff --git a/modules/listSearchSessions/listSearchSessions.mappable-params.iml.json b/modules/listSearchSessions/listSearchSessions.mappable-params.iml.json new file mode 100644 index 0000000..4009694 --- /dev/null +++ b/modules/listSearchSessions/listSearchSessions.mappable-params.iml.json @@ -0,0 +1,20 @@ +[ + { + "name": "take", + "label": "Max Results", + "type": "integer", + "default": 50, + "validate": { + "min": 1, + "max": 100 + } + }, + { + "name": "skip", + "label": "Skip (Offset)", + "type": "integer", + "validate": { + "min": 0 + } + } +] diff --git a/modules/listSearchSessions/listSearchSessions.samples.iml.json b/modules/listSearchSessions/listSearchSessions.samples.iml.json new file mode 100644 index 0000000..c4962d2 --- /dev/null +++ b/modules/listSearchSessions/listSearchSessions.samples.iml.json @@ -0,0 +1,6 @@ +{ + "id": "monthly-leads-az-85020", + "totalDelivered": 25, + "lastRequestAt": "2026-10-28T23:17:54.592Z", + "createdAt": "2026-10-01T16:02:11.120Z" +} diff --git a/modules/lookupProperty/lookupProperty.communication.iml.json b/modules/lookupProperty/lookupProperty.communication.iml.json new file mode 100644 index 0000000..2973986 --- /dev/null +++ b/modules/lookupProperty/lookupProperty.communication.iml.json @@ -0,0 +1,23 @@ +{ + "url": "/v1/property/lookup/all-attributes", + "method": "POST", + "body": { + "requests": [ + { + "address": "{{if(parameters.identifyBy == 'address', parameters.address, undefined)}}", + "propertyId": "{{if(parameters.identifyBy == 'propertyId', ifempty(parameters.propertyId, undefined), undefined)}}", + "hash": "{{if(parameters.identifyBy == 'hash', ifempty(parameters.hash, undefined), undefined)}}", + "apn": "{{if(parameters.identifyBy == 'apn', ifempty(parameters.apn, undefined), undefined)}}", + "countyFipsCode": "{{if(parameters.identifyBy == 'apn', ifempty(parameters.countyFipsCode, undefined), undefined)}}", + "requestId": "{{ifempty(parameters.requestId, undefined)}}" + } + ], + "options": { + "skipTrace": "{{if(parameters.includeSkipTrace == true, true, undefined)}}", + "datasets": "{{if(length(parameters.datasets) > 0, parameters.datasets, undefined)}}" + } + }, + "response": { + "output": "{{first(body.results.properties)}}" + } +} diff --git a/modules/lookupProperty/lookupProperty.interface.iml.json b/modules/lookupProperty/lookupProperty.interface.iml.json new file mode 100644 index 0000000..d15d78f --- /dev/null +++ b/modules/lookupProperty/lookupProperty.interface.iml.json @@ -0,0 +1,1689 @@ +[ + { + "name": "_id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "cityAliases", + "type": "array", + "label": "City aliases", + "spec": { + "type": "text" + } + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "localities", + "type": "array", + "label": "Localities", + "spec": { + "type": "text" + } + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County fips code" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "normalized", + "type": "boolean", + "label": "Normalized" + }, + { + "name": "geoStatus", + "type": "text", + "label": "Geo status" + }, + { + "name": "geoStatusCode", + "type": "text", + "label": "Geo status code" + } + ] + }, + { + "name": "assessment", + "type": "collection", + "label": "Assessment", + "spec": [ + { + "name": "assessedImprovementValue", + "type": "number", + "label": "Assessed improvement value" + }, + { + "name": "assessedLandValue", + "type": "number", + "label": "Assessed land value" + }, + { + "name": "totalAssessedValue", + "type": "number", + "label": "Total assessed value" + }, + { + "name": "assessmentYear", + "type": "number", + "label": "Assessment year" + }, + { + "name": "improvementMarketValue", + "type": "number", + "label": "Improvement market value" + }, + { + "name": "landMarketValue", + "type": "number", + "label": "Land market value" + }, + { + "name": "totalMarketValue", + "type": "number", + "label": "Total market value" + }, + { + "name": "marketValueYear", + "type": "number", + "label": "Market value year" + } + ] + }, + { + "name": "building", + "type": "collection", + "label": "Building", + "spec": [ + { + "name": "totalBuildingAreaSquareFeet", + "type": "number", + "label": "Total building area square feet" + }, + { + "name": "totalBuildingAreaCode", + "type": "text", + "label": "Total building area code" + }, + { + "name": "totalBuildingAreaCodeDescription", + "type": "text", + "label": "Total building area code description" + }, + { + "name": "livingAreaSquareFeet", + "type": "number", + "label": "Living area square feet" + }, + { + "name": "grossBuildingAreaSquareFeet", + "type": "number", + "label": "Gross building area square feet" + }, + { + "name": "adjustedBuildingAreaSquareFeet", + "type": "number", + "label": "Adjusted building area square feet" + }, + { + "name": "yearBuilt", + "type": "number", + "label": "Year built" + }, + { + "name": "effectiveYearBuilt", + "type": "number", + "label": "Effective year built" + }, + { + "name": "buildingCount", + "type": "number", + "label": "Building count" + }, + { + "name": "roomCount", + "type": "number", + "label": "Room count" + }, + { + "name": "unitCount", + "type": "number", + "label": "Unit count" + }, + { + "name": "calculatedBathroomCount", + "type": "number", + "label": "Calculated bathroom count" + }, + { + "name": "fullBathroomCount", + "type": "number", + "label": "Full bathroom count" + }, + { + "name": "bathroomCount", + "type": "number", + "label": "Bathroom count" + }, + { + "name": "bathFixtureCount", + "type": "number", + "label": "Bath fixture count" + }, + { + "name": "storyCount", + "type": "number", + "label": "Story count" + }, + { + "name": "garageParkingSpaceCount", + "type": "number", + "label": "Garage parking space count" + }, + { + "name": "residentialUnitCount", + "type": "number", + "label": "Residential unit count" + }, + { + "name": "features", + "type": "array", + "label": "Features", + "spec": { + "type": "text" + } + }, + { + "name": "airConditioningSourceCode", + "type": "text", + "label": "Air conditioning source code" + }, + { + "name": "airConditioningSource", + "type": "text", + "label": "Air conditioning source" + }, + { + "name": "buildingConditionCode", + "type": "text", + "label": "Building condition code" + }, + { + "name": "buildingCondition", + "type": "text", + "label": "Building condition" + }, + { + "name": "buildingQualityCode", + "type": "text", + "label": "Building quality code" + }, + { + "name": "buildingQuality", + "type": "text", + "label": "Building quality" + }, + { + "name": "constructionTypeCode", + "type": "text", + "label": "Construction type code" + }, + { + "name": "constructionType", + "type": "text", + "label": "Construction type" + }, + { + "name": "exteriorWallsCode", + "type": "text", + "label": "Exterior walls code" + }, + { + "name": "exteriorWalls", + "type": "text", + "label": "Exterior walls" + }, + { + "name": "garageCode", + "type": "text", + "label": "Garage code" + }, + { + "name": "garage", + "type": "text", + "label": "Garage" + }, + { + "name": "heatSourceCode", + "type": "text", + "label": "Heat source code" + }, + { + "name": "heatSource", + "type": "text", + "label": "Heat source" + }, + { + "name": "patioCode", + "type": "text", + "label": "Patio code" + }, + { + "name": "patio", + "type": "text", + "label": "Patio" + }, + { + "name": "poolCode", + "type": "text", + "label": "Pool code" + }, + { + "name": "pool", + "type": "text", + "label": "Pool" + }, + { + "name": "roofCoverCode", + "type": "text", + "label": "Roof cover code" + }, + { + "name": "roofCover", + "type": "text", + "label": "Roof cover" + }, + { + "name": "fireplaceCount", + "type": "number", + "label": "Fireplace count" + } + ] + }, + { + "name": "deedHistory", + "type": "array", + "label": "Deed history", + "spec": [ + { + "name": "buyers", + "type": "array", + "label": "Buyers", + "spec": { + "type": "text" + } + }, + { + "name": "sellers", + "type": "array", + "label": "Sellers", + "spec": { + "type": "text" + } + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + }, + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentTypeCode", + "type": "text", + "label": "Document type code" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "salePrice", + "type": "number", + "label": "Sale price" + }, + { + "name": "salePriceCode", + "type": "text", + "label": "Sale price code" + }, + { + "name": "transferTax", + "type": "number", + "label": "Transfer tax" + }, + { + "name": "ownerRelationshipType", + "type": "text", + "label": "Owner relationship type" + }, + { + "name": "multipleOwners", + "type": "boolean", + "label": "Multiple owners" + } + ] + }, + { + "name": "demographics", + "type": "collection", + "label": "Demographics", + "spec": [ + { + "name": "age", + "type": "number", + "label": "Age" + }, + { + "name": "emailDomain", + "type": "text", + "label": "Email domain" + }, + { + "name": "householdSize", + "type": "number", + "label": "Household size" + }, + { + "name": "income", + "type": "number", + "label": "Income" + }, + { + "name": "netWorth", + "type": "number", + "label": "Net worth" + }, + { + "name": "discretionaryIncome", + "type": "number", + "label": "Discretionary income" + }, + { + "name": "homeownerRenterCode", + "type": "text", + "label": "Homeowner renter code" + }, + { + "name": "homeownerRenter", + "type": "text", + "label": "Homeowner renter" + }, + { + "name": "genderCode", + "type": "text", + "label": "Gender code" + }, + { + "name": "gender", + "type": "text", + "label": "Gender" + }, + { + "name": "maritalStatusCode", + "type": "text", + "label": "Marital status code" + }, + { + "name": "maritalStatus", + "type": "text", + "label": "Marital status" + }, + { + "name": "childCount", + "type": "number", + "label": "Child count" + }, + { + "name": "hasChildren", + "type": "boolean", + "label": "Has children" + }, + { + "name": "investments", + "type": "array", + "label": "Investments", + "spec": { + "type": "text" + } + }, + { + "name": "individualEducationCode", + "type": "text", + "label": "Individual education code" + }, + { + "name": "individualEducation", + "type": "text", + "label": "Individual education" + }, + { + "name": "individualOccupationCode", + "type": "text", + "label": "Individual occupation code" + }, + { + "name": "individualOccupation", + "type": "text", + "label": "Individual occupation" + }, + { + "name": "businessOwnerCode", + "type": "text", + "label": "Business owner code" + }, + { + "name": "businessOwner", + "type": "text", + "label": "Business owner" + }, + { + "name": "petOwner", + "type": "boolean", + "label": "Pet owner" + }, + { + "name": "millionaire", + "type": "boolean", + "label": "Millionaire" + }, + { + "name": "recentlyDivorced", + "type": "boolean", + "label": "Recently divorced" + }, + { + "name": "singleParent", + "type": "boolean", + "label": "Single parent" + }, + { + "name": "smoker", + "type": "boolean", + "label": "Smoker" + }, + { + "name": "religious", + "type": "boolean", + "label": "Religious" + }, + { + "name": "religiousAffiliationCode", + "type": "text", + "label": "Religious affiliation code" + }, + { + "name": "religiousAffiliation", + "type": "text", + "label": "Religious affiliation" + }, + { + "name": "recentlyMoved", + "type": "boolean", + "label": "Recently moved" + }, + { + "name": "estimatedAreaCreditRating", + "type": "text", + "label": "Estimated area credit rating" + } + ] + }, + { + "name": "foreclosure", + "type": "collection", + "label": "Foreclosure", + "spec": [] + }, + { + "name": "permit", + "type": "collection", + "label": "Permit", + "spec": [ + { + "name": "permitCount", + "type": "number", + "label": "Permit count" + }, + { + "name": "earliestDate", + "type": "text", + "label": "Earliest date" + }, + { + "name": "latestDate", + "type": "text", + "label": "Latest date" + }, + { + "name": "totalJobValue", + "type": "number", + "label": "Total job value" + }, + { + "name": "allTags", + "type": "array", + "label": "All tags", + "spec": { + "type": "text" + } + }, + { + "name": "tags", + "type": "collection", + "label": "Tags", + "spec": [ + { + "name": "addition", + "type": "boolean", + "label": "Addition" + }, + { + "name": "adu", + "type": "boolean", + "label": "Adu" + }, + { + "name": "bathroom", + "type": "boolean", + "label": "Bathroom" + }, + { + "name": "battery", + "type": "boolean", + "label": "Battery" + }, + { + "name": "demolition", + "type": "boolean", + "label": "Demolition" + }, + { + "name": "electrical", + "type": "boolean", + "label": "Electrical" + }, + { + "name": "electricMeter", + "type": "boolean", + "label": "Electric meter" + }, + { + "name": "evCharger", + "type": "boolean", + "label": "Ev charger" + }, + { + "name": "fireSprinkler", + "type": "boolean", + "label": "Fire sprinkler" + }, + { + "name": "gas", + "type": "boolean", + "label": "Gas" + }, + { + "name": "generator", + "type": "boolean", + "label": "Generator" + }, + { + "name": "grading", + "type": "boolean", + "label": "Grading" + }, + { + "name": "heatPump", + "type": "boolean", + "label": "Heat pump" + }, + { + "name": "hvac", + "type": "boolean", + "label": "Hvac" + }, + { + "name": "inspectionPassed", + "type": "boolean", + "label": "Inspection passed" + }, + { + "name": "kitchen", + "type": "boolean", + "label": "Kitchen" + }, + { + "name": "newConstruction", + "type": "boolean", + "label": "New construction" + }, + { + "name": "plumbing", + "type": "boolean", + "label": "Plumbing" + }, + { + "name": "poolAndHotTub", + "type": "boolean", + "label": "Pool and hot tub" + }, + { + "name": "remodel", + "type": "boolean", + "label": "Remodel" + }, + { + "name": "roofing", + "type": "boolean", + "label": "Roofing" + }, + { + "name": "solar", + "type": "boolean", + "label": "Solar" + }, + { + "name": "waterHeater", + "type": "boolean", + "label": "Water heater" + }, + { + "name": "windowDoor", + "type": "boolean", + "label": "Window door" + } + ] + } + ] + }, + { + "name": "general", + "type": "collection", + "label": "General", + "spec": [ + { + "name": "carrierRoute", + "type": "text", + "label": "Carrier route" + }, + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + }, + { + "name": "mailingAddressVacant", + "type": "boolean", + "label": "Mailing address vacant" + }, + { + "name": "vacantDate", + "type": "text", + "label": "Vacant date" + }, + { + "name": "mobileHome", + "type": "boolean", + "label": "Mobile home" + }, + { + "name": "standardizedLandUseCode", + "type": "text", + "label": "Standardized land use code" + }, + { + "name": "propertyTypeCategory", + "type": "text", + "label": "Property type category" + }, + { + "name": "propertyTypeDetail", + "type": "text", + "label": "Property type detail" + }, + { + "name": "congressionalDistrict", + "type": "text", + "label": "Congressional district" + }, + { + "name": "timeZone", + "type": "text", + "label": "Time zone" + }, + { + "name": "utcOffset", + "type": "number", + "label": "Utc offset" + }, + { + "name": "daylightSavingsTime", + "type": "boolean", + "label": "Daylight savings time" + }, + { + "name": "censusTract", + "type": "text", + "label": "Census tract" + }, + { + "name": "primaryParcel", + "type": "boolean", + "label": "Primary parcel" + }, + { + "name": "parcelCount", + "type": "number", + "label": "Parcel count" + } + ] + }, + { + "name": "ids", + "type": "collection", + "label": "Ids", + "spec": [ + { + "name": "addressHash", + "type": "text", + "label": "Address hash" + }, + { + "name": "apn", + "type": "text", + "label": "Apn" + }, + { + "name": "oldApn", + "type": "text", + "label": "Old apn" + }, + { + "name": "fipsCode", + "type": "text", + "label": "Fips code" + } + ] + }, + { + "name": "legal", + "type": "collection", + "label": "Legal", + "spec": [ + { + "name": "assessorsMapReference", + "type": "text", + "label": "Assessors map reference" + }, + { + "name": "cityTownshipMunicipality", + "type": "text", + "label": "City township municipality" + }, + { + "name": "legalDescription", + "type": "text", + "label": "Legal description" + }, + { + "name": "lotCode", + "type": "text", + "label": "Lot code" + }, + { + "name": "lotNumber", + "type": "text", + "label": "Lot number" + }, + { + "name": "sectionTownshipRangeMeridian", + "type": "text", + "label": "Section township range meridian" + }, + { + "name": "subdivisionName", + "type": "text", + "label": "Subdivision name" + }, + { + "name": "tractNumber", + "type": "text", + "label": "Tract number" + } + ] + }, + { + "name": "lot", + "type": "collection", + "label": "Lot", + "spec": [ + { + "name": "lotSizeAcres", + "type": "number", + "label": "Lot size acres" + }, + { + "name": "lotSizeSquareFeet", + "type": "number", + "label": "Lot size square feet" + }, + { + "name": "zoningCode", + "type": "text", + "label": "Zoning code" + } + ] + }, + { + "name": "mortgageHistory", + "type": "array", + "label": "Mortgage history", + "spec": [ + { + "name": "borrowers", + "type": "array", + "label": "Borrowers", + "spec": { + "type": "text" + } + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "dueDate", + "type": "text", + "label": "Due date" + }, + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "lenderTypeCode", + "type": "text", + "label": "Lender type code" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTermMonths", + "type": "number", + "label": "Loan term months" + }, + { + "name": "interestRate", + "type": "text", + "label": "Interest rate" + } + ] + }, + { + "name": "openLien", + "type": "collection", + "label": "Open lien", + "spec": [ + { + "name": "allLoanTypes", + "type": "array", + "label": "All loan types", + "spec": { + "type": "text" + } + }, + { + "name": "juniorLoanTypes", + "type": "array", + "label": "Junior loan types", + "spec": { + "type": "text" + } + }, + { + "name": "totalOpenLienCount", + "type": "number", + "label": "Total open lien count" + }, + { + "name": "mortgages", + "type": "array", + "label": "Mortgages", + "spec": [ + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTermMonths", + "type": "number", + "label": "Loan term months" + }, + { + "name": "interestRate", + "type": "number", + "label": "Interest rate" + }, + { + "name": "loanType", + "type": "text", + "label": "Loan type" + }, + { + "name": "subordinateLoanFlag", + "type": "boolean", + "label": "Subordinate loan flag" + }, + { + "name": "equityFlag", + "type": "boolean", + "label": "Equity flag" + }, + { + "name": "helocFlag", + "type": "boolean", + "label": "Heloc flag" + }, + { + "name": "pudRider", + "type": "boolean", + "label": "Pud rider" + }, + { + "name": "condoRider", + "type": "boolean", + "label": "Condo rider" + }, + { + "name": "multiFamilyRider", + "type": "boolean", + "label": "Multi family rider" + }, + { + "name": "assumable", + "type": "boolean", + "label": "Assumable" + }, + { + "name": "privateLender", + "type": "boolean", + "label": "Private lender" + }, + { + "name": "secondHomeRider", + "type": "boolean", + "label": "Second home rider" + }, + { + "name": "sellerCarryBack", + "type": "boolean", + "label": "Seller carry back" + }, + { + "name": "balloonRider", + "type": "boolean", + "label": "Balloon rider" + } + ] + } + ] + }, + { + "name": "owner", + "type": "collection", + "label": "Owner", + "spec": [ + { + "name": "fullName", + "type": "text", + "label": "Full name" + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + } + ] + }, + { + "name": "names", + "type": "array", + "label": "Names", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + } + ] + }, + { + "name": "ownerOccupied", + "type": "boolean", + "label": "Owner occupied" + }, + { + "name": "mailingOptOut", + "type": "boolean", + "label": "Mailing opt out" + }, + { + "name": "hasLandline", + "type": "boolean", + "label": "Has landline" + }, + { + "name": "ownerStatusTypeCode", + "type": "text", + "label": "Owner status type code" + }, + { + "name": "ownerStatusType", + "type": "text", + "label": "Owner status type" + }, + { + "name": "ownershipRightsCode", + "type": "text", + "label": "Ownership rights code" + }, + { + "name": "ownershipRights", + "type": "text", + "label": "Ownership rights" + } + ] + }, + { + "name": "propertyOwnerProfile", + "type": "collection", + "label": "Property owner profile", + "spec": [ + { + "name": "averageYearBuilt", + "type": "number", + "label": "Average year built" + }, + { + "name": "propertiesCount", + "type": "number", + "label": "Properties count" + }, + { + "name": "propertiesTotalEquity", + "type": "number", + "label": "Properties total equity" + }, + { + "name": "propertiesTotalEstimatedValue", + "type": "number", + "label": "Properties total estimated value" + } + ] + }, + { + "name": "quickLists", + "type": "collection", + "label": "Quick lists", + "spec": [ + { + "name": "absenteeOwner", + "type": "boolean", + "label": "Absentee owner" + }, + { + "name": "absenteeOwnerInState", + "type": "boolean", + "label": "Absentee owner in state" + }, + { + "name": "absenteeOwnerOutOfState", + "type": "boolean", + "label": "Absentee owner out of state" + }, + { + "name": "activeListing", + "type": "boolean", + "label": "Active listing" + }, + { + "name": "activeAuction", + "type": "boolean", + "label": "Active auction" + }, + { + "name": "cashBuyer", + "type": "boolean", + "label": "Cash buyer" + }, + { + "name": "corporateOwned", + "type": "boolean", + "label": "Corporate owned" + }, + { + "name": "expiredListing", + "type": "boolean", + "label": "Expired listing" + }, + { + "name": "freeAndClear", + "type": "boolean", + "label": "Free and clear" + }, + { + "name": "highEquity", + "type": "boolean", + "label": "High equity" + }, + { + "name": "inherited", + "type": "boolean", + "label": "Inherited" + }, + { + "name": "listedBelowMarketPrice", + "type": "boolean", + "label": "Listed below market price" + }, + { + "name": "lowEquity", + "type": "boolean", + "label": "Low equity" + }, + { + "name": "mailingAddressVacant", + "type": "boolean", + "label": "Mailing address vacant" + }, + { + "name": "onMarket", + "type": "boolean", + "label": "On market" + }, + { + "name": "outOfStateOwner", + "type": "boolean", + "label": "Out of state owner" + }, + { + "name": "ownerOccupied", + "type": "boolean", + "label": "Owner occupied" + }, + { + "name": "pendingListing", + "type": "boolean", + "label": "Pending listing" + }, + { + "name": "preforeclosure", + "type": "boolean", + "label": "Preforeclosure" + }, + { + "name": "recentlySold", + "type": "boolean", + "label": "Recently sold" + }, + { + "name": "samePropertyAndMailingAddress", + "type": "boolean", + "label": "Same property and mailing address" + }, + { + "name": "taxDefault", + "type": "boolean", + "label": "Tax default" + }, + { + "name": "tiredLandlord", + "type": "boolean", + "label": "Tired landlord" + }, + { + "name": "unknownEquity", + "type": "boolean", + "label": "Unknown equity" + }, + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + }, + { + "name": "hasHoa", + "type": "boolean", + "label": "Has hoa" + }, + { + "name": "hasHoaFees", + "type": "boolean", + "label": "Has hoa fees" + }, + { + "name": "canceledListing", + "type": "boolean", + "label": "Canceled listing" + }, + { + "name": "noticeOfSale", + "type": "boolean", + "label": "Notice of sale" + }, + { + "name": "noticeOfDefault", + "type": "boolean", + "label": "Notice of default" + }, + { + "name": "noticeOfLisPendens", + "type": "boolean", + "label": "Notice of lis pendens" + } + ] + }, + { + "name": "sale", + "type": "collection", + "label": "Sale", + "spec": [ + { + "name": "lastTransfer", + "type": "collection", + "label": "Last transfer", + "spec": [ + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "priceCodeDescription", + "type": "text", + "label": "Price code description" + }, + { + "name": "priceCode", + "type": "text", + "label": "Price code" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + } + ] + }, + { + "name": "lastSale", + "type": "collection", + "label": "Last sale", + "spec": [ + { + "name": "saleBuyers", + "type": "array", + "label": "Sale buyers", + "spec": { + "type": "text" + } + }, + { + "name": "saleSellers", + "type": "array", + "label": "Sale sellers", + "spec": { + "type": "text" + } + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + }, + { + "name": "flipLength", + "type": "number", + "label": "Flip length" + }, + { + "name": "flipLengthCategory", + "type": "number", + "label": "Flip length category" + }, + { + "name": "flipProfit", + "type": "number", + "label": "Flip profit" + }, + { + "name": "priorTransfer", + "type": "collection", + "label": "Prior transfer", + "spec": [ + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "priceCodeDescription", + "type": "text", + "label": "Price code description" + }, + { + "name": "priceCode", + "type": "text", + "label": "Price code" + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + }, + { + "name": "priorSale", + "type": "collection", + "label": "Prior sale", + "spec": [ + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleBuyers", + "type": "array", + "label": "Sale buyers", + "spec": { + "type": "text" + } + }, + { + "name": "saleSellers", + "type": "array", + "label": "Sale sellers", + "spec": { + "type": "text" + } + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + } + ] + }, + { + "name": "tax", + "type": "collection", + "label": "Tax", + "spec": [ + { + "name": "taxAmount", + "type": "number", + "label": "Tax amount" + }, + { + "name": "taxYear", + "type": "number", + "label": "Tax year" + }, + { + "name": "taxDelinquentYear", + "type": "number", + "label": "Tax delinquent year" + }, + { + "name": "taxRateCodeArea", + "type": "text", + "label": "Tax rate code area" + } + ] + }, + { + "name": "valuation", + "type": "collection", + "label": "Valuation", + "spec": [ + { + "name": "estimatedValue", + "type": "number", + "label": "Estimated value" + }, + { + "name": "priceRangeMin", + "type": "number", + "label": "Price range min" + }, + { + "name": "priceRangeMax", + "type": "number", + "label": "Price range max" + }, + { + "name": "standardDeviation", + "type": "number", + "label": "Standard deviation" + }, + { + "name": "confidenceScore", + "type": "number", + "label": "Confidence score" + }, + { + "name": "asOfDate", + "type": "text", + "label": "As of date" + }, + { + "name": "equityCurrentEstimatedBalance", + "type": "number", + "label": "Equity current estimated balance" + }, + { + "name": "ltv", + "type": "number", + "label": "Ltv" + }, + { + "name": "equityPercent", + "type": "number", + "label": "Equity percent" + } + ] + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "propertyDateModified", + "type": "text", + "label": "Property date modified" + } + ] + } +] diff --git a/modules/lookupProperty/lookupProperty.mappable-params.iml.json b/modules/lookupProperty/lookupProperty.mappable-params.iml.json new file mode 100644 index 0000000..df72785 --- /dev/null +++ b/modules/lookupProperty/lookupProperty.mappable-params.iml.json @@ -0,0 +1,155 @@ +[ + { + "name": "identifyBy", + "type": "select", + "label": "Identify property by", + "required": true, + "default": "address", + "options": [ + { + "label": "Address", + "value": "address" + }, + { + "label": "Property ID", + "value": "propertyId" + }, + { + "label": "Address hash", + "value": "hash" + }, + { + "label": "APN and county FIPS code", + "value": "apn" + } + ], + "help": "Only the identifier chosen here is sent. The other identifier fields are ignored." + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State", + "help": "Two-letter state code, for example AZ." + }, + { + "name": "zip", + "type": "text", + "label": "ZIP code" + } + ] + }, + { + "name": "propertyId", + "type": "text", + "label": "Property ID" + }, + { + "name": "hash", + "type": "text", + "label": "Address hash" + }, + { + "name": "apn", + "type": "text", + "label": "APN" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County FIPS code" + }, + { + "name": "datasets", + "type": "array", + "label": "Datasets", + "help": "Extra data to include. \"basic\" and \"core\" cannot be combined. Leave empty for the default set.", + "spec": { + "type": "select", + "options": [ + { + "label": "basic", + "value": "basic" + }, + { + "label": "comps", + "value": "comps" + }, + { + "label": "batchrank", + "value": "batchrank" + }, + { + "label": "contact", + "value": "contact" + }, + { + "label": "core", + "value": "core" + }, + { + "label": "deed", + "value": "deed" + }, + { + "label": "demographic", + "value": "demographic" + }, + { + "label": "foreclosure", + "value": "foreclosure" + }, + { + "label": "listing", + "value": "listing" + }, + { + "label": "mortgage-liens", + "value": "mortgage-liens" + }, + { + "label": "owner", + "value": "owner" + }, + { + "label": "permit", + "value": "permit" + }, + { + "label": "quicklist", + "value": "quicklist" + }, + { + "label": "valuation", + "value": "valuation" + } + ] + } + }, + { + "name": "includeSkipTrace", + "type": "boolean", + "label": "Include skip trace", + "help": "Skip trace is billed separately for each matched record." + }, + { + "name": "requestId", + "type": "text", + "label": "Request ID", + "help": "Optional value returned with the result so you can match it later." + } +] diff --git a/modules/lookupProperty/lookupProperty.samples.iml.json b/modules/lookupProperty/lookupProperty.samples.iml.json new file mode 100644 index 0000000..ef051bf --- /dev/null +++ b/modules/lookupProperty/lookupProperty.samples.iml.json @@ -0,0 +1,537 @@ +{ + "_id": "76ef71f2bc0c999e87592cd8302c5d32", + "address": { + "houseNumber": "26823", + "street": "26823 N 31st Dr", + "city": "Phoenix", + "cityAliases": [ + "phoenix" + ], + "county": "Maricopa", + "state": "AZ", + "zip": "85083", + "zipPlus4": "5837", + "formattedStreet": "N 31st Dr", + "streetNoUnit": "26823 N 31st Dr", + "localities": [ + "arizona hillcrest" + ], + "latitude": 33.728785, + "longitude": -112.126019, + "countyFipsCode": "04013", + "hash": "fae16e6adfaf0bb0f306ba89bf7b4c1e", + "normalized": true, + "geoStatus": "Rooftop", + "geoStatusCode": "A" + }, + "assessment": { + "assessedImprovementValue": 27730, + "assessedLandValue": 6930, + "totalAssessedValue": 43300, + "assessmentYear": 2024, + "improvementMarketValue": 346400, + "landMarketValue": 86600, + "totalMarketValue": 433000, + "marketValueYear": 2024 + }, + "building": { + "totalBuildingAreaSquareFeet": 1761, + "totalBuildingAreaCode": "B", + "totalBuildingAreaCodeDescription": "Living Area", + "livingAreaSquareFeet": 1761, + "grossBuildingAreaSquareFeet": 2480, + "adjustedBuildingAreaSquareFeet": 2100, + "yearBuilt": 2001, + "effectiveYearBuilt": 2001, + "buildingCount": 1, + "roomCount": 6, + "unitCount": 1, + "calculatedBathroomCount": 2, + "fullBathroomCount": 2, + "bathroomCount": 2, + "bathFixtureCount": 7, + "storyCount": 1, + "garageParkingSpaceCount": 2, + "residentialUnitCount": 1, + "features": [ + "Air Conditioning", + "Refrigeration Air Conditioning", + "Sauna/Steam Room", + "Koi Pond", + "Mobile Home Hookup", + "Patio", + "Garage", + "Pool", + "Elevator" + ], + "airConditioningSourceCode": "J", + "airConditioningSource": "Refrigeration", + "buildingConditionCode": "F", + "buildingCondition": "Average", + "buildingQualityCode": "D", + "buildingQuality": "B", + "constructionTypeCode": "C", + "constructionType": "Frame", + "exteriorWallsCode": "S", + "exteriorWalls": "Stucco", + "garageCode": "I", + "garage": "Garage", + "heatSourceCode": "X", + "heatSource": "Yes", + "patioCode": "A", + "patio": "Patio", + "poolCode": "H", + "pool": "Pool - Yes", + "roofCoverCode": "I", + "roofCover": "Concrete", + "fireplaceCount": 0 + }, + "deedHistory": [ + { + "buyers": [ + "KB HOME SALES PHOENIX INC" + ], + "sellers": [ + "KB HOME PHOENIX INC", + "KAUFMAN & BROAD OF ARIZONA INC" + ], + "recordingDate": "2001-07-30T00:00:00.000Z", + "saleDate": "2001-07-20T00:00:00.000Z", + "documentNumber": "2001-0688814", + "documentTypeCode": "Z", + "documentType": "Corporation Deed", + "salePrice": 0, + "salePriceCode": "Full Consideration", + "transferTax": 412, + "ownerRelationshipType": "Arm's Length", + "multipleOwners": false + }, + { + "buyers": [ + "CHENEY RHONDA" + ], + "sellers": [ + "NUNEZ JR ROBERTO" + ], + "recordingDate": "2002-08-30T00:00:00.000Z", + "saleDate": "2002-08-19T00:00:00.000Z", + "documentNumber": "2002-0890956", + "documentTypeCode": "BF", + "documentType": "Quit Claim Deed", + "salePrice": 0 + }, + { + "buyers": [ + "ANDERSON RHONDA K" + ], + "sellers": [ + "ANDERSON KYLE DOUGLAS" + ], + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleDate": "2005-01-14T00:00:00.000Z", + "documentNumber": "2005-0080877", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + }, + { + "buyers": [ + "ANDERSON RHONDA K" + ], + "sellers": [ + "ANDERSON RHONDA K", + "CHENEY RHONDA" + ], + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleDate": "2005-01-14T00:00:00.000Z", + "documentNumber": "2005-0080876", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + }, + { + "buyers": [ + "ANDERSON KYLE", + "ANDERSON RHONDA K" + ], + "sellers": [], + "recordingDate": "2006-12-29T00:00:00.000Z", + "saleDate": "2006-12-11T00:00:00.000Z", + "documentNumber": "2006-1700576", + "documentTypeCode": "DN", + "documentType": "Stand Alone Mortgage" + }, + { + "buyers": [ + "ANDRESON RHONDA K", + "ANDERSON KYLE" + ], + "sellers": [ + "ANDRESON RHONDA K", + "ANDRESON KYLE" + ], + "recordingDate": "2013-01-18T00:00:00.000Z", + "saleDate": "2012-11-13T00:00:00.000Z", + "documentNumber": "2013-0056003", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + } + ], + "demographics": { + "age": 52, + "emailDomain": "gmail.com", + "householdSize": 3, + "income": 95000, + "netWorth": 425000, + "discretionaryIncome": 38000, + "homeownerRenterCode": "O", + "homeownerRenter": "Home Owner", + "genderCode": "M", + "gender": "Male", + "maritalStatusCode": "M", + "maritalStatus": "Married", + "childCount": 2, + "hasChildren": true, + "investments": [ + "Real Estate", + "Personal" + ], + "individualEducationCode": "BA", + "individualEducation": "Bachelor's Degree", + "individualOccupationCode": "PRO", + "individualOccupation": "Professional", + "businessOwnerCode": "A", + "businessOwner": "Real Estate", + "petOwner": true, + "millionaire": false, + "recentlyDivorced": false, + "singleParent": false, + "smoker": false, + "religious": true, + "religiousAffiliationCode": "C", + "religiousAffiliation": "Catholic", + "recentlyMoved": false, + "estimatedAreaCreditRating": "B" + }, + "foreclosure": {}, + "permit": { + "permitCount": 3, + "earliestDate": "2015-04-10", + "latestDate": "2022-08-22", + "totalJobValue": 48500, + "allTags": [ + "roofing", + "solar", + "electrical" + ], + "tags": { + "addition": false, + "adu": false, + "bathroom": false, + "battery": false, + "demolition": false, + "electrical": true, + "electricMeter": false, + "evCharger": false, + "fireSprinkler": false, + "gas": false, + "generator": false, + "grading": false, + "heatPump": false, + "hvac": false, + "inspectionPassed": true, + "kitchen": false, + "newConstruction": false, + "plumbing": false, + "poolAndHotTub": false, + "remodel": false, + "roofing": true, + "solar": true, + "waterHeater": false, + "windowDoor": false + } + }, + "general": { + "carrierRoute": "R087", + "vacant": false, + "mailingAddressVacant": false, + "vacantDate": "2021-05-14", + "mobileHome": false, + "standardizedLandUseCode": "R2", + "propertyTypeCategory": "Residential", + "propertyTypeDetail": "Single Family", + "congressionalDistrict": "8", + "timeZone": "Mountain", + "utcOffset": -7, + "daylightSavingsTime": false, + "censusTract": "040136119.001008", + "primaryParcel": true, + "parcelCount": 1 + }, + "ids": { + "addressHash": "fae16e6adfaf0bb0f306ba89bf7b4c1e", + "apn": "205-03-224", + "oldApn": "205032242", + "fipsCode": "04013" + }, + "legal": { + "assessorsMapReference": "MB 51824", + "cityTownshipMunicipality": "Phoenix", + "legalDescription": "ARIZONA HILLCREST MCR 518-24", + "lotCode": "S", + "lotNumber": "199", + "sectionTownshipRangeMeridian": "SEC 35 TWN 5N RNG 2E", + "subdivisionName": "ARIZONA HILLCREST", + "tractNumber": "ARIZONA HILLCREST" + }, + "lot": { + "lotSizeAcres": 0.14637281910009184, + "lotSizeSquareFeet": 6376, + "zoningCode": "R-8" + }, + "mortgageHistory": [ + { + "borrowers": [ + "CHENEY RHONDA" + ], + "saleDate": "2002-08-19T00:00:00.000Z", + "recordingDate": "2002-08-30T00:00:00.000Z", + "dueDate": "2032-09-01T00:00:00.000Z", + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": "6.43" + }, + { + "borrowers": [ + "ANDERSON RHONDA K" + ], + "saleDate": "2005-01-14T00:00:00.000Z", + "recordingDate": "2005-01-20T00:00:00.000Z", + "dueDate": "2035-02-01T00:00:00.000Z", + "lenderName": "JPMORGAN CHASE BANK NA", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "AH", + "loanType": "Unknown (DEFAULT)", + "loanAmount": 156800, + "loanTermMonths": 360, + "interestRate": "5.67" + }, + { + "borrowers": [ + "ANDERSON RHONDA K" + ], + "saleDate": "2005-01-14T00:00:00.000Z", + "recordingDate": "2005-01-20T00:00:00.000Z", + "dueDate": "2035-01-28T00:00:00.000Z", + "lenderName": "JPMORGAN CHASE BANK NA", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 19000, + "loanTermMonths": 360, + "interestRate": "5.67" + }, + { + "borrowers": [ + "ANDERSON KYLE", + "ANDERSON RHONDA K" + ], + "saleDate": "2006-12-11T00:00:00.000Z", + "recordingDate": "2006-12-29T00:00:00.000Z", + "dueDate": "2026-12-19T00:00:00.000Z", + "lenderName": "USAA FSB", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 75299, + "loanTermMonths": 240, + "interestRate": "6.33" + }, + { + "borrowers": [ + "ANDRESON RHONDA K", + "ANDERSON KYLE" + ], + "saleDate": "2012-11-13T00:00:00.000Z", + "recordingDate": "2013-01-18T00:00:00.000Z", + "dueDate": "2027-12-01T00:00:00.000Z", + "lenderName": "USAA FSB", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "U", + "loanType": "New Conventional", + "loanAmount": 112000, + "loanTermMonths": 180, + "interestRate": "3.31" + } + ], + "openLien": { + "allLoanTypes": [], + "juniorLoanTypes": [], + "totalOpenLienCount": 1, + "mortgages": [ + { + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": 6.43, + "loanType": "New Conventional", + "subordinateLoanFlag": false, + "equityFlag": false, + "helocFlag": false, + "pudRider": false, + "condoRider": false, + "multiFamilyRider": false, + "assumable": false, + "privateLender": false, + "secondHomeRider": false, + "sellerCarryBack": false, + "balloonRider": false + } + ] + }, + "owner": { + "fullName": "Kyle Anderson; Rhonda K Anderson", + "mailingAddress": { + "houseNumber": "26823", + "street": "26823 N 31st Dr", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85083", + "zipPlus4": "5837", + "formattedStreet": "N 31st Dr", + "streetNoUnit": "26823 N 31st Dr", + "hash": "fae16e6adfaf0bb0f306ba89bf7b4c1e" + }, + "names": [ + { + "first": "Kyle", + "last": "Anderson", + "full": "Kyle Anderson" + }, + { + "first": "Rhonda", + "middle": "K", + "last": "Anderson", + "full": "Rhonda K Anderson" + } + ], + "ownerOccupied": true, + "mailingOptOut": false, + "hasLandline": true, + "ownerStatusTypeCode": "B", + "ownerStatusType": "Individual", + "ownershipRightsCode": "AT", + "ownershipRights": "Joint Tenants" + }, + "propertyOwnerProfile": { + "averageYearBuilt": 2001, + "propertiesCount": 1, + "propertiesTotalEquity": 449821, + "propertiesTotalEstimatedValue": 449821 + }, + "quickLists": { + "absenteeOwner": false, + "absenteeOwnerInState": false, + "absenteeOwnerOutOfState": false, + "activeListing": false, + "activeAuction": false, + "cashBuyer": false, + "corporateOwned": false, + "expiredListing": false, + "freeAndClear": true, + "highEquity": true, + "inherited": false, + "listedBelowMarketPrice": false, + "lowEquity": false, + "mailingAddressVacant": false, + "onMarket": false, + "outOfStateOwner": false, + "ownerOccupied": true, + "pendingListing": false, + "preforeclosure": false, + "recentlySold": false, + "samePropertyAndMailingAddress": true, + "taxDefault": false, + "tiredLandlord": false, + "unknownEquity": false, + "vacant": false, + "hasHoa": true, + "hasHoaFees": true, + "canceledListing": false, + "noticeOfSale": false, + "noticeOfDefault": false, + "noticeOfLisPendens": false + }, + "sale": { + "lastTransfer": { + "documentNumber": "2013-0056003", + "documentType": "Intra-Family Transfer", + "priceCodeDescription": "Transfer Tax on document indicated as EXEMPT.", + "priceCode": "Z", + "recordingDate": "2013-01-18T00:00:00.000Z", + "saleDate": "2012-11-13T00:00:00.000Z" + }, + "lastSale": { + "saleBuyers": [ + "Michael R Torres" + ], + "saleSellers": [ + "BlueSky Holdings LLC" + ], + "salePriceIsEstimated": false + }, + "flipLength": 0, + "flipLengthCategory": 0, + "flipProfit": 0, + "priorTransfer": { + "documentNumber": "2005-0080877", + "documentType": "Intra-Family Transfer", + "priceCodeDescription": "Non-arms length transaction.", + "priceCode": "N", + "salePriceIsEstimated": false + }, + "priorSale": { + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleBuyers": [ + "BlueSky Holdings LLC" + ], + "saleSellers": [ + "Estate Of Dorothy J Miller" + ], + "salePriceIsEstimated": false + } + }, + "tax": { + "taxAmount": 19, + "taxYear": 2023, + "taxDelinquentYear": 2022, + "taxRateCodeArea": "971300" + }, + "valuation": { + "estimatedValue": 449821, + "priceRangeMin": 382348, + "priceRangeMax": 517294, + "standardDeviation": 12, + "confidenceScore": 88, + "asOfDate": "2024-02-28T00:00:00.000Z", + "equityCurrentEstimatedBalance": 449821, + "ltv": 0, + "equityPercent": 100 + }, + "meta": { + "propertyDateModified": "2024-04-11T09:54:59.574Z" + } +} diff --git a/modules/makeAnApiCall/makeAnApiCall.communication.iml.json b/modules/makeAnApiCall/makeAnApiCall.communication.iml.json new file mode 100644 index 0000000..bf865d7 --- /dev/null +++ b/modules/makeAnApiCall/makeAnApiCall.communication.iml.json @@ -0,0 +1,19 @@ +{ + "url": "/{{if(contains(lower(parameters.url), 'wallet') || contains(lower(parameters.url), 'async') || contains(lower(parameters.url), '..') || contains(lower(parameters.url), '%') || contains(lower(parameters.url), '?') || contains(lower(parameters.url), '://') || contains(lower(ifempty(parameters.body, '')), 'webhook') || contains(lower(ifempty(parameters.body, '')), 'u00') || contains(lower(parameters.url), '//') || contains(lower(parameters.url), '#') || (contains(lower(parameters.url), 'property-subscription') && (parameters.method == 'POST' || parameters.method == 'PUT' || parameters.method == 'PATCH')), 'blocked/not-available', trim(parameters.url))}}", + "method": "{{parameters.method}}", + "headers": { + "{{...}}": "{{toCollection(parameters.headers, 'key', 'value')}}" + }, + "qs": { + "{{...}}": "{{if(contains(lower(parameters.url), 'wallet') || contains(lower(parameters.url), 'async') || contains(lower(parameters.url), '..') || contains(lower(parameters.url), '%') || contains(lower(parameters.url), '?') || contains(lower(parameters.url), '://') || contains(lower(ifempty(parameters.body, '')), 'webhook') || contains(lower(ifempty(parameters.body, '')), 'u00') || contains(lower(parameters.url), '//') || contains(lower(parameters.url), '#') || (contains(lower(parameters.url), 'property-subscription') && (parameters.method == 'POST' || parameters.method == 'PUT' || parameters.method == 'PATCH')), undefined, toCollection(parameters.qs, 'key', 'value'))}}" + }, + "body": "{{if(contains(lower(parameters.url), 'wallet') || contains(lower(parameters.url), 'async') || contains(lower(parameters.url), '..') || contains(lower(parameters.url), '%') || contains(lower(parameters.url), '?') || contains(lower(parameters.url), '://') || contains(lower(ifempty(parameters.body, '')), 'webhook') || contains(lower(ifempty(parameters.body, '')), 'u00') || contains(lower(parameters.url), '//') || contains(lower(parameters.url), '#') || (contains(lower(parameters.url), 'property-subscription') && (parameters.method == 'POST' || parameters.method == 'PUT' || parameters.method == 'PATCH')) || length(ifempty(parameters.body, '')) == 0, undefined, parseJSON(parameters.body))}}", + "type": "json", + "response": { + "output": { + "statusCode": "{{statusCode}}", + "headers": "{{headers}}", + "body": "{{body}}" + } + } +} diff --git a/modules/makeAnApiCall/makeAnApiCall.interface.iml.json b/modules/makeAnApiCall/makeAnApiCall.interface.iml.json new file mode 100644 index 0000000..175f6e0 --- /dev/null +++ b/modules/makeAnApiCall/makeAnApiCall.interface.iml.json @@ -0,0 +1,18 @@ +[ + { + "name": "statusCode", + "type": "number", + "label": "Status code" + }, + { + "name": "headers", + "type": "collection", + "label": "Headers", + "spec": [] + }, + { + "name": "body", + "type": "any", + "label": "Body" + } +] diff --git a/modules/makeAnApiCall/makeAnApiCall.mappable-params.iml.json b/modules/makeAnApiCall/makeAnApiCall.mappable-params.iml.json new file mode 100644 index 0000000..4cbdb70 --- /dev/null +++ b/modules/makeAnApiCall/makeAnApiCall.mappable-params.iml.json @@ -0,0 +1,79 @@ +[ + { + "name": "url", + "type": "text", + "label": "URL", + "required": true, + "help": "Path after /api, without a leading slash, for example v1/property/search. Account balance (wallet) endpoints are not available here." + }, + { + "name": "method", + "type": "select", + "label": "Method", + "required": true, + "default": "GET", + "options": [ + { + "label": "GET", + "value": "GET" + }, + { + "label": "POST", + "value": "POST" + }, + { + "label": "PUT", + "value": "PUT" + }, + { + "label": "PATCH", + "value": "PATCH" + }, + { + "label": "DELETE", + "value": "DELETE" + } + ] + }, + { + "name": "headers", + "type": "array", + "label": "Headers", + "help": "The authorization header is added for you.", + "spec": [ + { + "name": "key", + "type": "text", + "label": "Key" + }, + { + "name": "value", + "type": "text", + "label": "Value" + } + ] + }, + { + "name": "qs", + "type": "array", + "label": "Query string", + "spec": [ + { + "name": "key", + "type": "text", + "label": "Key" + }, + { + "name": "value", + "type": "text", + "label": "Value" + } + ] + }, + { + "name": "body", + "type": "text", + "label": "Body", + "help": "JSON request body, as text. Requests that contain a callback URL field are not sent." + } +] diff --git a/modules/makeAnApiCall/makeAnApiCall.samples.iml.json b/modules/makeAnApiCall/makeAnApiCall.samples.iml.json new file mode 100644 index 0000000..781d097 --- /dev/null +++ b/modules/makeAnApiCall/makeAnApiCall.samples.iml.json @@ -0,0 +1,5 @@ +{ + "statusCode": 200, + "headers": {}, + "body": {} +} diff --git a/modules/reverseGeocode/reverseGeocode.communication.iml.json b/modules/reverseGeocode/reverseGeocode.communication.iml.json new file mode 100644 index 0000000..e4ef39e --- /dev/null +++ b/modules/reverseGeocode/reverseGeocode.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v1/address/reverse-geocode", + "method": "POST", + "body": { + "request": { + "latitude": "{{ifempty(parameters.latitude, undefined)}}", + "longitude": "{{ifempty(parameters.longitude, undefined)}}" + }, + "options": { + "showPropertyOnly": "{{if(parameters.propertyOnly == true, true, undefined)}}" + } + }, + "response": { + "iterate": "{{body.result.addresses}}", + "output": "{{item}}" + } +} diff --git a/modules/reverseGeocode/reverseGeocode.interface.iml.json b/modules/reverseGeocode/reverseGeocode.interface.iml.json new file mode 100644 index 0000000..4e670b6 --- /dev/null +++ b/modules/reverseGeocode/reverseGeocode.interface.iml.json @@ -0,0 +1,62 @@ +[ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "types", + "type": "array", + "label": "Types", + "spec": { + "type": "text" + } + }, + { + "name": "geoStatus", + "type": "text", + "label": "Geo status" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "error", + "type": "boolean", + "label": "Error" + } + ] + } +] diff --git a/modules/reverseGeocode/reverseGeocode.mappable-params.iml.json b/modules/reverseGeocode/reverseGeocode.mappable-params.iml.json new file mode 100644 index 0000000..31904b8 --- /dev/null +++ b/modules/reverseGeocode/reverseGeocode.mappable-params.iml.json @@ -0,0 +1,19 @@ +[ + { + "name": "latitude", + "label": "Latitude", + "required": true, + "type": "number" + }, + { + "name": "longitude", + "label": "Longitude", + "required": true, + "type": "number" + }, + { + "name": "propertyOnly", + "label": "Property Addresses Only", + "type": "boolean" + } +] diff --git a/modules/reverseGeocode/reverseGeocode.samples.iml.json b/modules/reverseGeocode/reverseGeocode.samples.iml.json new file mode 100644 index 0000000..e25e710 --- /dev/null +++ b/modules/reverseGeocode/reverseGeocode.samples.iml.json @@ -0,0 +1,16 @@ +{ + "street": "2806 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa", + "latitude": 33.4787348, + "longitude": -112.0306996, + "types": [ + "Street" + ], + "geoStatus": "Rooftop", + "meta": { + "error": false + } +} diff --git a/modules/reverseSkipTrace/reverseSkipTrace.communication.iml.json b/modules/reverseSkipTrace/reverseSkipTrace.communication.iml.json new file mode 100644 index 0000000..8d98e0b --- /dev/null +++ b/modules/reverseSkipTrace/reverseSkipTrace.communication.iml.json @@ -0,0 +1,23 @@ +{ + "url": "/v3/property/reverse-skip-trace", + "method": "POST", + "body": { + "requests": [ + { + "phone": "{{ifempty(parameters.phone, undefined)}}", + "email": "{{ifempty(parameters.email, undefined)}}", + "requestId": "{{ifempty(parameters.requestId, undefined)}}" + } + ], + "options": { + "includeTCPABlacklistedPhones": "{{if(parameters.includeTcpaFlagged == true, true, false)}}" + } + }, + "response": { + "iterate": { + "container": "{{body.result.data}}", + "condition": "{{item.meta.matched == true}}" + }, + "output": "{{item}}" + } +} diff --git a/modules/reverseSkipTrace/reverseSkipTrace.interface.iml.json b/modules/reverseSkipTrace/reverseSkipTrace.interface.iml.json new file mode 100644 index 0000000..20da1a3 --- /dev/null +++ b/modules/reverseSkipTrace/reverseSkipTrace.interface.iml.json @@ -0,0 +1,100 @@ +[ + { + "name": "input", + "type": "collection", + "label": "Input", + "spec": [ + { + "name": "phone", + "type": "text", + "label": "Phone" + }, + { + "name": "requestId", + "type": "text", + "label": "Request id" + } + ] + }, + { + "name": "persons", + "type": "array", + "label": "Persons", + "spec": [ + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + } + ] + }, + { + "name": "phoneNumbers", + "type": "array", + "label": "Phone numbers", + "spec": [ + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "tcpa", + "type": "boolean", + "label": "Tcpa" + }, + { + "name": "dnc", + "type": "boolean", + "label": "Dnc" + } + ] + }, + { + "name": "emails", + "type": "array", + "label": "Emails", + "spec": { + "type": "text" + } + } + ] + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "matched", + "type": "boolean", + "label": "Matched" + }, + { + "name": "error", + "type": "boolean", + "label": "Error" + } + ] + } +] diff --git a/modules/reverseSkipTrace/reverseSkipTrace.mappable-params.iml.json b/modules/reverseSkipTrace/reverseSkipTrace.mappable-params.iml.json new file mode 100644 index 0000000..b81ce3d --- /dev/null +++ b/modules/reverseSkipTrace/reverseSkipTrace.mappable-params.iml.json @@ -0,0 +1,26 @@ +[ + { + "name": "phone", + "label": "Phone Number", + "help": "A US phone number. Fill in a phone number or an email address.", + "type": "text" + }, + { + "name": "email", + "label": "Email Address", + "type": "text" + }, + { + "name": "requestId", + "label": "Your Reference ID", + "help": "Optional. Echoed back in the result.", + "type": "text" + }, + { + "name": "includeTcpaFlagged", + "label": "Include TCPA-Flagged Phones", + "help": "Off by default: phone numbers flagged as TCPA litigators are left out. Turn on only with a documented compliance basis. Flagged numbers carry \"tcpa\": true in the result.", + "type": "boolean", + "default": false + } +] diff --git a/modules/reverseSkipTrace/reverseSkipTrace.samples.iml.json b/modules/reverseSkipTrace/reverseSkipTrace.samples.iml.json new file mode 100644 index 0000000..4b22739 --- /dev/null +++ b/modules/reverseSkipTrace/reverseSkipTrace.samples.iml.json @@ -0,0 +1,30 @@ +{ + "input": { + "phone": "6025550142", + "requestId": "ref-1" + }, + "persons": [ + { + "name": { + "first": "Dana", + "last": "Reyes", + "full": "Dana Reyes" + }, + "phoneNumbers": [ + { + "number": "6025550142", + "type": "Mobile", + "tcpa": false, + "dnc": false + } + ], + "emails": [ + "dana@example.com" + ] + } + ], + "meta": { + "matched": true, + "error": false + } +} diff --git a/modules/searchProperties/searchProperties.communication.iml.json b/modules/searchProperties/searchProperties.communication.iml.json new file mode 100644 index 0000000..0f02025 --- /dev/null +++ b/modules/searchProperties/searchProperties.communication.iml.json @@ -0,0 +1,18 @@ +{ + "url": "/v1/property/search", + "method": "POST", + "body": { + "searchCriteria": "{{omit(parameters, 'take', 'skip')}}", + "options": { + "take": "{{ifempty(parameters.take, 25)}}", + "skip": "{{ifempty(parameters.skip, undefined)}}" + } + }, + "response": { + "iterate": "{{body.results.properties}}", + "output": { + "{{...}}": "{{item}}", + "searchMeta": "{{body.results.meta.results}}" + } + } +} diff --git a/modules/searchProperties/searchProperties.interface.iml.json b/modules/searchProperties/searchProperties.interface.iml.json new file mode 100644 index 0000000..6aeca0f --- /dev/null +++ b/modules/searchProperties/searchProperties.interface.iml.json @@ -0,0 +1,1423 @@ +[ + { + "name": "_id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "addressValidity", + "type": "text", + "label": "Address validity" + }, + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County fips code" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + } + ] + }, + { + "name": "deedHistory", + "type": "array", + "label": "Deed history", + "spec": [ + { + "name": "buyers", + "type": "array", + "label": "Buyers", + "spec": { + "type": "text" + } + }, + { + "name": "sellers", + "type": "array", + "label": "Sellers", + "spec": { + "type": "text" + } + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + }, + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentTypeCode", + "type": "text", + "label": "Document type code" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "salePrice", + "type": "number", + "label": "Sale price" + }, + { + "name": "salePriceCode", + "type": "text", + "label": "Sale price code" + }, + { + "name": "transferTax", + "type": "number", + "label": "Transfer tax" + }, + { + "name": "ownerRelationshipType", + "type": "text", + "label": "Owner relationship type" + }, + { + "name": "multipleOwners", + "type": "boolean", + "label": "Multiple owners" + }, + { + "name": "transactionType", + "type": "text", + "label": "Transaction type" + }, + { + "name": "transactionTypeCode", + "type": "text", + "label": "Transaction type code" + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + } + ] + } + ] + }, + { + "name": "demographics", + "type": "collection", + "label": "Demographics", + "spec": [ + { + "name": "age", + "type": "number", + "label": "Age" + }, + { + "name": "emailDomain", + "type": "text", + "label": "Email domain" + }, + { + "name": "householdSize", + "type": "number", + "label": "Household size" + }, + { + "name": "income", + "type": "number", + "label": "Income" + }, + { + "name": "netWorth", + "type": "number", + "label": "Net worth" + }, + { + "name": "discretionaryIncome", + "type": "number", + "label": "Discretionary income" + }, + { + "name": "homeownerRenterCode", + "type": "text", + "label": "Homeowner renter code" + }, + { + "name": "homeownerRenter", + "type": "text", + "label": "Homeowner renter" + }, + { + "name": "genderCode", + "type": "text", + "label": "Gender code" + }, + { + "name": "gender", + "type": "text", + "label": "Gender" + }, + { + "name": "maritalStatusCode", + "type": "text", + "label": "Marital status code" + }, + { + "name": "maritalStatus", + "type": "text", + "label": "Marital status" + }, + { + "name": "religious", + "type": "boolean", + "label": "Religious" + }, + { + "name": "religiousAffiliationCode", + "type": "text", + "label": "Religious affiliation code" + }, + { + "name": "religiousAffiliation", + "type": "text", + "label": "Religious affiliation" + }, + { + "name": "individualEducationCode", + "type": "text", + "label": "Individual education code" + }, + { + "name": "individualEducation", + "type": "text", + "label": "Individual education" + }, + { + "name": "individualOccupation", + "type": "text", + "label": "Individual occupation" + }, + { + "name": "individualOccupationCode", + "type": "text", + "label": "Individual occupation code" + }, + { + "name": "estimatedAreaCreditRating", + "type": "text", + "label": "Estimated area credit rating" + } + ] + }, + { + "name": "foreclosure", + "type": "collection", + "label": "Foreclosure", + "spec": [ + { + "name": "statusCode", + "type": "text", + "label": "Status code" + }, + { + "name": "status", + "type": "text", + "label": "Status" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "filingDate", + "type": "text", + "label": "Filing date" + }, + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentTypeCode", + "type": "text", + "label": "Document type code" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "releaseDate", + "type": "text", + "label": "Release date" + } + ] + }, + { + "name": "ids", + "type": "collection", + "label": "Ids", + "spec": [ + { + "name": "apn", + "type": "text", + "label": "Apn" + } + ] + }, + { + "name": "intel", + "type": "collection", + "label": "Intel", + "spec": [ + { + "name": "salePropensity", + "type": "number", + "label": "Sale propensity" + }, + { + "name": "salePropensityStatus", + "type": "text", + "label": "Sale propensity status" + }, + { + "name": "salePropensityCategory", + "type": "text", + "label": "Sale propensity category" + } + ] + }, + { + "name": "involuntaryLien", + "type": "collection", + "label": "Involuntary lien", + "spec": [ + { + "name": "liens", + "type": "array", + "label": "Liens", + "spec": [ + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "documentTypeCode", + "type": "text", + "label": "Document type code" + }, + { + "name": "lienType", + "type": "text", + "label": "Lien type" + }, + { + "name": "lienTypeCode", + "type": "text", + "label": "Lien type code" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + } + ] + } + ] + }, + { + "name": "listing", + "type": "collection", + "label": "Listing", + "spec": [ + { + "name": "status", + "type": "text", + "label": "Status" + }, + { + "name": "price", + "type": "number", + "label": "Price" + }, + { + "name": "rental", + "type": "boolean", + "label": "Rental" + }, + { + "name": "originalListingDate", + "type": "text", + "label": "Original listing date" + }, + { + "name": "maxListPrice", + "type": "number", + "label": "Max list price" + }, + { + "name": "maxListPriceDate", + "type": "text", + "label": "Max list price date" + }, + { + "name": "minListPrice", + "type": "number", + "label": "Min list price" + }, + { + "name": "minListPriceDate", + "type": "text", + "label": "Min list price date" + }, + { + "name": "soldPrice", + "type": "number", + "label": "Sold price" + }, + { + "name": "soldDate", + "type": "text", + "label": "Sold date" + }, + { + "name": "failedListingDate", + "type": "text", + "label": "Failed listing date" + }, + { + "name": "bedroomCount", + "type": "number", + "label": "Bedroom count" + }, + { + "name": "bathroomCount", + "type": "number", + "label": "Bathroom count" + }, + { + "name": "totalBuildingAreaSquareFeet", + "type": "number", + "label": "Total building area square feet" + }, + { + "name": "lotSizeSquareFeet", + "type": "number", + "label": "Lot size square feet" + }, + { + "name": "propertyType", + "type": "text", + "label": "Property type" + }, + { + "name": "yearBuilt", + "type": "number", + "label": "Year built" + }, + { + "name": "appliances", + "type": "array", + "label": "Appliances", + "spec": { + "type": "text" + } + }, + { + "name": "roofTypes", + "type": "array", + "label": "Roof types", + "spec": { + "type": "text" + } + }, + { + "name": "coolingTypes", + "type": "array", + "label": "Cooling types", + "spec": { + "type": "text" + } + }, + { + "name": "heatingTypes", + "type": "array", + "label": "Heating types", + "spec": { + "type": "text" + } + }, + { + "name": "newConstruction", + "type": "boolean", + "label": "New construction" + }, + { + "name": "listingUrl", + "type": "text", + "label": "Listing url" + }, + { + "name": "livingArea", + "type": "number", + "label": "Living area" + }, + { + "name": "taxes", + "type": "array", + "label": "Taxes", + "spec": [ + { + "name": "amount", + "type": "number", + "label": "Amount" + }, + { + "name": "year", + "type": "number", + "label": "Year" + } + ] + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + }, + { + "name": "mortgageHistory", + "type": "array", + "label": "Mortgage history", + "spec": [ + { + "name": "borrowers", + "type": "array", + "label": "Borrowers", + "spec": { + "type": "text" + } + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "dueDate", + "type": "text", + "label": "Due date" + }, + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "lenderTypeCode", + "type": "text", + "label": "Lender type code" + }, + { + "name": "loanTypeCode", + "type": "text", + "label": "Loan type code" + }, + { + "name": "loanType", + "type": "text", + "label": "Loan type" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTermMonths", + "type": "number", + "label": "Loan term months" + }, + { + "name": "interestRate", + "type": "text", + "label": "Interest rate" + }, + { + "name": "transactionType", + "type": "text", + "label": "Transaction type" + }, + { + "name": "transactionTypeCode", + "type": "text", + "label": "Transaction type code" + }, + { + "name": "documentDate", + "type": "text", + "label": "Document date" + } + ] + }, + { + "name": "openLien", + "type": "collection", + "label": "Open lien", + "spec": [ + { + "name": "totalOpenLienCount", + "type": "number", + "label": "Total open lien count" + }, + { + "name": "mortgages", + "type": "array", + "label": "Mortgages", + "spec": [ + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTermMonths", + "type": "number", + "label": "Loan term months" + }, + { + "name": "interestRate", + "type": "number", + "label": "Interest rate" + }, + { + "name": "loanType", + "type": "text", + "label": "Loan type" + }, + { + "name": "subordinateLoanFlag", + "type": "boolean", + "label": "Subordinate loan flag" + }, + { + "name": "equityFlag", + "type": "boolean", + "label": "Equity flag" + }, + { + "name": "helocFlag", + "type": "boolean", + "label": "Heloc flag" + }, + { + "name": "pudRider", + "type": "boolean", + "label": "Pud rider" + }, + { + "name": "condoRider", + "type": "boolean", + "label": "Condo rider" + }, + { + "name": "multiFamilyRider", + "type": "boolean", + "label": "Multi family rider" + }, + { + "name": "assumable", + "type": "boolean", + "label": "Assumable" + }, + { + "name": "privateLender", + "type": "boolean", + "label": "Private lender" + }, + { + "name": "secondHomeRider", + "type": "boolean", + "label": "Second home rider" + }, + { + "name": "sellerCarryBack", + "type": "boolean", + "label": "Seller carry back" + }, + { + "name": "balloonRider", + "type": "boolean", + "label": "Balloon rider" + } + ] + } + ] + }, + { + "name": "owner", + "type": "collection", + "label": "Owner", + "spec": [ + { + "name": "fullName", + "type": "text", + "label": "Full name" + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [ + { + "name": "addressValidity", + "type": "text", + "label": "Address validity" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "deliveryPointCode", + "type": "text", + "label": "Delivery point code" + }, + { + "name": "dpvMatchCode", + "type": "text", + "label": "Dpv match code" + }, + { + "name": "dpvFootnotes", + "type": "text", + "label": "Dpv footnotes" + } + ] + }, + { + "name": "names", + "type": "array", + "label": "Names", + "spec": [ + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + } + ] + }, + { + "name": "mailingOptOut", + "type": "boolean", + "label": "Mailing opt out" + }, + { + "name": "hasLandline", + "type": "boolean", + "label": "Has landline" + } + ] + }, + { + "name": "permit", + "type": "collection", + "label": "Permit", + "spec": [ + { + "name": "permitCount", + "type": "number", + "label": "Permit count" + }, + { + "name": "earliestDate", + "type": "text", + "label": "Earliest date" + }, + { + "name": "latestDate", + "type": "text", + "label": "Latest date" + }, + { + "name": "totalJobValue", + "type": "number", + "label": "Total job value" + }, + { + "name": "allTags", + "type": "array", + "label": "All tags", + "spec": { + "type": "text" + } + }, + { + "name": "tags", + "type": "collection", + "label": "Tags", + "spec": [ + { + "name": "addition", + "type": "boolean", + "label": "Addition" + }, + { + "name": "adu", + "type": "boolean", + "label": "Adu" + }, + { + "name": "bathroom", + "type": "boolean", + "label": "Bathroom" + }, + { + "name": "battery", + "type": "boolean", + "label": "Battery" + }, + { + "name": "demolition", + "type": "boolean", + "label": "Demolition" + }, + { + "name": "electrical", + "type": "boolean", + "label": "Electrical" + }, + { + "name": "electricMeter", + "type": "boolean", + "label": "Electric meter" + }, + { + "name": "evCharger", + "type": "boolean", + "label": "Ev charger" + }, + { + "name": "fireSprinkler", + "type": "boolean", + "label": "Fire sprinkler" + }, + { + "name": "gas", + "type": "boolean", + "label": "Gas" + }, + { + "name": "generator", + "type": "boolean", + "label": "Generator" + }, + { + "name": "grading", + "type": "boolean", + "label": "Grading" + }, + { + "name": "heatPump", + "type": "boolean", + "label": "Heat pump" + }, + { + "name": "hvac", + "type": "boolean", + "label": "Hvac" + }, + { + "name": "inspectionPassed", + "type": "boolean", + "label": "Inspection passed" + }, + { + "name": "kitchen", + "type": "boolean", + "label": "Kitchen" + }, + { + "name": "newConstruction", + "type": "boolean", + "label": "New construction" + }, + { + "name": "plumbing", + "type": "boolean", + "label": "Plumbing" + }, + { + "name": "poolAndHotTub", + "type": "boolean", + "label": "Pool and hot tub" + }, + { + "name": "remodel", + "type": "boolean", + "label": "Remodel" + }, + { + "name": "roofing", + "type": "boolean", + "label": "Roofing" + }, + { + "name": "solar", + "type": "boolean", + "label": "Solar" + }, + { + "name": "waterHeater", + "type": "boolean", + "label": "Water heater" + }, + { + "name": "windowDoor", + "type": "boolean", + "label": "Window door" + } + ] + } + ] + }, + { + "name": "propertyOwnerProfile", + "type": "collection", + "label": "Property owner profile", + "spec": [ + { + "name": "averageYearBuilt", + "type": "number", + "label": "Average year built" + }, + { + "name": "propertiesCount", + "type": "number", + "label": "Properties count" + }, + { + "name": "mortgagesTotalBalance", + "type": "number", + "label": "Mortgages total balance" + }, + { + "name": "mortgagesCount", + "type": "number", + "label": "Mortgages count" + }, + { + "name": "mortgagesAverageBalance", + "type": "number", + "label": "Mortgages average balance" + } + ] + }, + { + "name": "quickLists", + "type": "collection", + "label": "Quick lists", + "spec": [ + { + "name": "absenteeOwner", + "type": "boolean", + "label": "Absentee owner" + }, + { + "name": "absenteeOwnerInState", + "type": "boolean", + "label": "Absentee owner in state" + }, + { + "name": "absenteeOwnerOutOfState", + "type": "boolean", + "label": "Absentee owner out of state" + }, + { + "name": "activeListing", + "type": "boolean", + "label": "Active listing" + }, + { + "name": "activeAuction", + "type": "boolean", + "label": "Active auction" + }, + { + "name": "cashBuyer", + "type": "boolean", + "label": "Cash buyer" + }, + { + "name": "corporateOwned", + "type": "boolean", + "label": "Corporate owned" + }, + { + "name": "expiredListing", + "type": "boolean", + "label": "Expired listing" + }, + { + "name": "failedListing", + "type": "boolean", + "label": "Failed listing" + }, + { + "name": "freeAndClear", + "type": "boolean", + "label": "Free and clear" + }, + { + "name": "fixAndFlip", + "type": "boolean", + "label": "Fix and flip" + }, + { + "name": "forSaleByOwner", + "type": "boolean", + "label": "For sale by owner" + }, + { + "name": "highEquity", + "type": "boolean", + "label": "High equity" + }, + { + "name": "inherited", + "type": "boolean", + "label": "Inherited" + }, + { + "name": "involuntaryLien", + "type": "boolean", + "label": "Involuntary lien" + }, + { + "name": "listedBelowMarketPrice", + "type": "boolean", + "label": "Listed below market price" + }, + { + "name": "lowEquity", + "type": "boolean", + "label": "Low equity" + }, + { + "name": "mailingAddressVacant", + "type": "boolean", + "label": "Mailing address vacant" + }, + { + "name": "onMarket", + "type": "boolean", + "label": "On market" + }, + { + "name": "outOfStateOwner", + "type": "boolean", + "label": "Out of state owner" + }, + { + "name": "ownerOccupied", + "type": "boolean", + "label": "Owner occupied" + }, + { + "name": "pendingListing", + "type": "boolean", + "label": "Pending listing" + }, + { + "name": "preforeclosure", + "type": "boolean", + "label": "Preforeclosure" + }, + { + "name": "recentlySold", + "type": "boolean", + "label": "Recently sold" + }, + { + "name": "samePropertyAndMailingAddress", + "type": "boolean", + "label": "Same property and mailing address" + }, + { + "name": "seniorOwner", + "type": "boolean", + "label": "Senior owner" + }, + { + "name": "taxDefault", + "type": "boolean", + "label": "Tax default" + }, + { + "name": "tiredLandlord", + "type": "boolean", + "label": "Tired landlord" + }, + { + "name": "unknownEquity", + "type": "boolean", + "label": "Unknown equity" + }, + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + }, + { + "name": "vacantLot", + "type": "boolean", + "label": "Vacant lot" + }, + { + "name": "hasHoa", + "type": "boolean", + "label": "Has hoa" + }, + { + "name": "hasHoaFees", + "type": "boolean", + "label": "Has hoa fees" + }, + { + "name": "canceledListing", + "type": "boolean", + "label": "Canceled listing" + }, + { + "name": "noticeOfSale", + "type": "boolean", + "label": "Notice of sale" + }, + { + "name": "noticeOfDefault", + "type": "boolean", + "label": "Notice of default" + }, + { + "name": "noticeOfLisPendens", + "type": "boolean", + "label": "Notice of lis pendens" + } + ] + }, + { + "name": "sale", + "type": "collection", + "label": "Sale", + "spec": [ + { + "name": "priorSale", + "type": "collection", + "label": "Prior sale", + "spec": [ + { + "name": "mortgages", + "type": "array", + "label": "Mortgages", + "spec": [ + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "pageNumber", + "type": "text", + "label": "Page number" + }, + { + "name": "bookNumber", + "type": "text", + "label": "Book number" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTypeCode", + "type": "text", + "label": "Loan type code" + }, + { + "name": "loanType", + "type": "text", + "label": "Loan type" + }, + { + "name": "financingTypeCode", + "type": "text", + "label": "Financing type code" + }, + { + "name": "financingType", + "type": "text", + "label": "Financing type" + }, + { + "name": "interestRate", + "type": "number", + "label": "Interest rate" + }, + { + "name": "dueDate", + "type": "text", + "label": "Due date" + }, + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "lenderTypeCode", + "type": "text", + "label": "Lender type code" + }, + { + "name": "loanTerm", + "type": "text", + "label": "Loan term" + } + ] + } + ] + } + ] + }, + { + "name": "valuation", + "type": "collection", + "label": "Valuation", + "spec": [ + { + "name": "estimatedValue", + "type": "number", + "label": "Estimated value" + }, + { + "name": "priceRangeMin", + "type": "number", + "label": "Price range min" + }, + { + "name": "priceRangeMax", + "type": "number", + "label": "Price range max" + }, + { + "name": "standardDeviation", + "type": "number", + "label": "Standard deviation" + }, + { + "name": "confidenceScore", + "type": "number", + "label": "Confidence score" + }, + { + "name": "asOfDate", + "type": "text", + "label": "As of date" + }, + { + "name": "equityCurrentEstimatedBalance", + "type": "number", + "label": "Equity current estimated balance" + }, + { + "name": "ltv", + "type": "number", + "label": "Ltv" + }, + { + "name": "equityPercent", + "type": "number", + "label": "Equity percent" + } + ] + }, + { + "name": "searchMeta", + "type": "collection", + "label": "Search meta", + "spec": [ + { + "name": "resultCount", + "type": "number", + "label": "Result count" + }, + { + "name": "resultsFound", + "type": "number", + "label": "Results found" + } + ] + } +] diff --git a/modules/searchProperties/searchProperties.mappable-params.iml.json b/modules/searchProperties/searchProperties.mappable-params.iml.json new file mode 100644 index 0000000..fc9dab5 --- /dev/null +++ b/modules/searchProperties/searchProperties.mappable-params.iml.json @@ -0,0 +1,716 @@ +[ + { + "name": "query", + "type": "text", + "label": "Location or text query", + "help": "City, county, ZIP code or address text to search in. Leave empty to rely on the filters below." + }, + { + "name": "quickLists", + "type": "array", + "label": "Quick lists (all must match)", + "help": "Prebuilt lead lists. Choose a \"not-\" entry to exclude a list.", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + }, + { + "label": "not-absentee-owner", + "value": "not-absentee-owner" + }, + { + "label": "not-active-auction", + "value": "not-active-auction" + }, + { + "label": "not-active-listing", + "value": "not-active-listing" + }, + { + "label": "not-canceled-listing", + "value": "not-canceled-listing" + }, + { + "label": "not-cash-buyer", + "value": "not-cash-buyer" + }, + { + "label": "not-corporate-owned", + "value": "not-corporate-owned" + }, + { + "label": "not-expired-listing", + "value": "not-expired-listing" + }, + { + "label": "not-failed-listing", + "value": "not-failed-listing" + }, + { + "label": "not-fix-and-flip", + "value": "not-fix-and-flip" + }, + { + "label": "not-free-and-clear", + "value": "not-free-and-clear" + }, + { + "label": "not-for-sale-by-owner", + "value": "not-for-sale-by-owner" + }, + { + "label": "not-has-hoa", + "value": "not-has-hoa" + }, + { + "label": "not-has-hoa-fees", + "value": "not-has-hoa-fees" + }, + { + "label": "not-high-equity", + "value": "not-high-equity" + }, + { + "label": "not-inherited", + "value": "not-inherited" + }, + { + "label": "not-involuntary-lien", + "value": "not-involuntary-lien" + }, + { + "label": "not-in-state-absentee-owner", + "value": "not-in-state-absentee-owner" + }, + { + "label": "not-listed-below-market-price", + "value": "not-listed-below-market-price" + }, + { + "label": "not-low-equity", + "value": "not-low-equity" + }, + { + "label": "not-mailing-address-vacant", + "value": "not-mailing-address-vacant" + }, + { + "label": "not-notice-of-default", + "value": "not-notice-of-default" + }, + { + "label": "not-notice-of-lis-pendens", + "value": "not-notice-of-lis-pendens" + }, + { + "label": "not-notice-of-sale", + "value": "not-notice-of-sale" + }, + { + "label": "not-on-market", + "value": "not-on-market" + }, + { + "label": "not-out-of-state-absentee-owner", + "value": "not-out-of-state-absentee-owner" + }, + { + "label": "not-out-of-state-owner", + "value": "not-out-of-state-owner" + }, + { + "label": "not-owner-occupied", + "value": "not-owner-occupied" + }, + { + "label": "not-pending-listing", + "value": "not-pending-listing" + }, + { + "label": "not-preforeclosure", + "value": "not-preforeclosure" + }, + { + "label": "not-recently-sold", + "value": "not-recently-sold" + }, + { + "label": "not-same-property-and-mailing-address", + "value": "not-same-property-and-mailing-address" + }, + { + "label": "not-senior-owner", + "value": "not-senior-owner" + }, + { + "label": "not-tax-default", + "value": "not-tax-default" + }, + { + "label": "not-tired-landlord", + "value": "not-tired-landlord" + }, + { + "label": "not-trust-owned", + "value": "not-trust-owned" + }, + { + "label": "not-unknown-equity", + "value": "not-unknown-equity" + }, + { + "label": "not-vacant", + "value": "not-vacant" + }, + { + "label": "not-vacant-lot", + "value": "not-vacant-lot" + } + ] + } + }, + { + "name": "orQuickLists", + "type": "array", + "label": "Quick lists (any may match)", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "building", + "type": "collection", + "label": "Building", + "spec": [ + { + "name": "bedroomCount", + "type": "collection", + "label": "Bedrooms", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + }, + { + "name": "bathroomCount", + "type": "collection", + "label": "Bathrooms", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + }, + { + "name": "yearBuilt", + "type": "collection", + "label": "Year built", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + }, + { + "name": "livingAreaSquareFeet", + "type": "collection", + "label": "Living area (sq ft)", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + } + ] + }, + { + "name": "general", + "type": "collection", + "label": "Property type", + "spec": [ + { + "name": "propertyTypeCategory", + "type": "collection", + "label": "Property type category", + "spec": [ + { + "name": "equals", + "type": "text", + "label": "Equals", + "help": "For example Single Family, Multi Family, Condo." + } + ] + }, + { + "name": "propertyTypeDetail", + "type": "collection", + "label": "Property type detail", + "spec": [ + { + "name": "equals", + "type": "text", + "label": "Equals" + } + ] + } + ] + }, + { + "name": "lot", + "type": "collection", + "label": "Lot", + "spec": [ + { + "name": "lotSizeAcres", + "type": "collection", + "label": "Lot size (acres)", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + } + ] + }, + { + "name": "valuation", + "type": "collection", + "label": "Valuation", + "spec": [ + { + "name": "estimatedValue", + "type": "collection", + "label": "Estimated value", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + }, + { + "name": "equityPercent", + "type": "collection", + "label": "Equity percent", + "spec": [ + { + "name": "min", + "type": "number", + "label": "Minimum" + }, + { + "name": "max", + "type": "number", + "label": "Maximum" + } + ] + } + ] + }, + { + "name": "sale", + "type": "collection", + "label": "Sale", + "spec": [ + { + "name": "lastSaleDate", + "type": "collection", + "label": "Last sale date", + "spec": [ + { + "name": "minDate", + "type": "text", + "label": "From (YYYY-MM-DD)" + }, + { + "name": "maxDate", + "type": "text", + "label": "To (YYYY-MM-DD)" + } + ] + } + ] + }, + { + "name": "owner", + "type": "collection", + "label": "Owner", + "spec": [ + { + "name": "ownerOccupied", + "type": "collection", + "label": "Owner occupied", + "spec": [ + { + "name": "equals", + "type": "boolean", + "label": "Equals" + } + ] + } + ] + }, + { + "name": "take", + "type": "uinteger", + "label": "Max results", + "default": 25, + "validate": { + "max": 100 + }, + "help": "Up to 100 per run. You are billed per record returned, and a value of 0 is billed as 1 record." + }, + { + "name": "skip", + "type": "uinteger", + "label": "Skip", + "help": "Number of records to skip, for paging." + } +] diff --git a/modules/searchProperties/searchProperties.samples.iml.json b/modules/searchProperties/searchProperties.samples.iml.json new file mode 100644 index 0000000..0ce1135 --- /dev/null +++ b/modules/searchProperties/searchProperties.samples.iml.json @@ -0,0 +1,575 @@ +{ + "_id": "5c2b4e5fdca9199c5e2a041c2e8166e1", + "address": { + "addressValidity": "Valid", + "houseNumber": "4522", + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85086", + "zipPlus4": "1431", + "latitude": 33.867445, + "longitude": -112.157639, + "countyFipsCode": "04013", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + }, + "deedHistory": [ + { + "buyers": [ + "TOUSA HOMES INC", + "ENGLE HOMES" + ], + "sellers": [ + "ACACIA CREDIT FUND 8-A LLC" + ], + "recordingDate": "2005-03-10T00:00:00.000Z", + "saleDate": "2005-03-04T00:00:00.000Z", + "documentNumber": "2005.294894", + "documentTypeCode": "S", + "documentType": "Cash Sale Deed", + "salePrice": 252492, + "salePriceCode": "Full Consideration", + "transferTax": 412, + "ownerRelationshipType": "Arm's Length", + "multipleOwners": false, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "3150 S 48th St Ste 100", + "city": "Phoenix", + "state": "AZ", + "zip": "85040", + "hash": "9612f33c41b36a5e163072df7d8535c1" + } + }, + { + "buyers": [ + "BARNES DAVID", + "BARNES TRISHA N" + ], + "sellers": [ + "ENGLE/SUNBELT LLC" + ], + "recordingDate": "2005-11-17T00:00:00.000Z", + "saleDate": "2005-11-14T00:00:00.000Z", + "documentNumber": "2005-1744001", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 422475, + "newConstruction": true, + "transactionType": "New construction residential transactions", + "transactionTypeCode": "C", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "BARNES DAVID" + ], + "sellers": [ + "BARNES DAVID", + "BARNES TRISHA N" + ], + "recordingDate": "2006-02-21T00:00:00.000Z", + "saleDate": "2006-02-13T00:00:00.000Z", + "documentNumber": "2006-0236246", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true, + "transactionType": "Non-arms length transactions with valid title company name (no accomodations)", + "transactionTypeCode": "A", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "ROMERO MARIA ESTELA", + "ROMERO OCTAVIO R" + ], + "sellers": [ + "BARNES DAVID" + ], + "recordingDate": "2010-10-27T00:00:00.000Z", + "saleDate": "2010-09-03T00:00:00.000Z", + "documentNumber": "2010-0941098", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 154000, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "THE FIVE CLUB TRUST" + ], + "sellers": [ + "ROMERO OCTAVIO RIMOLDI", + "ROMERO MARIA ESTELA" + ], + "recordingDate": "2015-10-23T00:00:00.000Z", + "saleDate": "2015-09-09T00:00:00.000Z", + "documentNumber": "2015-0764207", + "documentTypeCode": "Q", + "documentType": "Beneficiary Deed (Buyer ID = BE)", + "salePrice": 0, + "transactionType": "Non-arms length transactions with valid title company name (no accomodations)", + "transactionTypeCode": "A" + }, + { + "buyers": [ + "MADISON NATHAN S", + "MADISON MARY L" + ], + "sellers": [ + "ROMERO OCTAVIO RIMOLDI", + "ROMERO MARIA ESTELA" + ], + "recordingDate": "2020-08-11T00:00:00.000Z", + "saleDate": "2020-08-06T00:00:00.000Z", + "documentNumber": "2020.732181", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 389900, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "YUET P AND I-JUNG K POON FAMILY TRUST" + ], + "sellers": [ + "MADISON NATHAN S", + "MADISON MARY L" + ], + "recordingDate": "2022-04-11T00:00:00.000Z", + "saleDate": "2022-03-31T00:00:00.000Z", + "documentNumber": "2022.316997", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 570000, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "19675 La Mar Dr", + "city": "Cupertino", + "state": "CA", + "zip": "95014", + "hash": "f3b3f6919555dd565a3a8eeee61b15b4" + } + }, + { + "buyers": [ + "4522 W RAVINA LLC" + ], + "sellers": [ + "YUET P AND I-JUNG K POON FAMILY TRUST" + ], + "recordingDate": "2023-02-09T00:00:00.000Z", + "saleDate": "2023-02-09T00:00:00.000Z", + "documentNumber": "2023.67061", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 0, + "transactionType": "Non-arms length transactions with valid title company name (no accomodations)", + "transactionTypeCode": "A", + "mailingAddress": { + "street": "3225 McLeod Dr # 777", + "city": "Las Vegas", + "state": "NV", + "zip": "89121", + "hash": "45ba83fc378f6c2ee5a655f19dd58860" + } + } + ], + "demographics": { + "age": 60, + "emailDomain": "gmail.com", + "householdSize": 1, + "income": 170000, + "netWorth": 437000, + "discretionaryIncome": 64750, + "homeownerRenterCode": "O", + "homeownerRenter": "Home Owner", + "genderCode": "F", + "gender": "Female", + "maritalStatusCode": "S", + "maritalStatus": "Single", + "religious": true, + "religiousAffiliationCode": "P", + "religiousAffiliation": "Protestant", + "individualEducationCode": "E", + "individualEducation": "Attended College", + "individualOccupation": "Professional / Technical", + "individualOccupationCode": "1", + "estimatedAreaCreditRating": "A" + }, + "foreclosure": { + "statusCode": "E", + "status": "Rescission Recording", + "recordingDate": "2010-11-12T00:00:00.000Z", + "filingDate": "2010-11-12T00:00:00.000Z", + "documentNumber": "2010-0991603", + "documentTypeCode": "L", + "documentType": "Notice of Rescission", + "releaseDate": "2010-11-12T00:00:00.000Z" + }, + "ids": { + "apn": "203-04-206" + }, + "intel": { + "salePropensity": 12.8, + "salePropensityStatus": "Available", + "salePropensityCategory": "Medium" + }, + "involuntaryLien": { + "liens": [ + { + "documentNumber": "225074972", + "documentType": "Financing Statement", + "documentTypeCode": "ED", + "lienType": "Economic Lien", + "lienTypeCode": "B", + "recordingDate": "2025-07-02T00:00:00.000Z" + } + ] + }, + "listing": { + "status": "Off Market", + "price": 2700, + "rental": false, + "originalListingDate": "2025-04-29T00:00:00.000Z", + "maxListPrice": 570000, + "maxListPriceDate": "2022-04-11T00:00:00.000Z", + "minListPrice": 2450, + "minListPriceDate": "2020-06-04T00:00:00.000Z", + "soldPrice": 570000, + "soldDate": "2022-04-11T00:00:00.000Z", + "failedListingDate": "2025-05-01T00:00:00.000Z", + "bedroomCount": 5, + "bathroomCount": 3, + "totalBuildingAreaSquareFeet": 2906, + "lotSizeSquareFeet": 6969, + "propertyType": "Single Family", + "yearBuilt": 2005, + "appliances": [ + "Dishwasher", + "Dryer", + "Garbage disposal", + "Microwave", + "Refrigerator", + "Washer" + ], + "roofTypes": [ + "Tile" + ], + "coolingTypes": [ + "Other" + ], + "heatingTypes": [ + "Forced air", + "Gas" + ], + "newConstruction": false, + "listingUrl": "https://www.zillow.com/homedetails/4522-W-Ravina-Ln-Anthem-AZ-85086/67774980_zpid/", + "livingArea": 2906, + "taxes": [ + { + "amount": 2263.56, + "year": 2024 + }, + { + "amount": 2732.26, + "year": 2023 + }, + { + "amount": 2684.5, + "year": 2022 + }, + { + "amount": 2708, + "year": 2021 + }, + { + "amount": 2629.9, + "year": 2020 + }, + { + "amount": 2547.14, + "year": 2019 + }, + { + "amount": 2547.14, + "year": 2018 + }, + { + "amount": 2464.6, + "year": 2017 + }, + { + "amount": 2095.58, + "year": 2016 + }, + { + "amount": 2095.58, + "year": 2015 + }, + { + "year": 2014 + }, + { + "year": 2013 + }, + { + "year": 2012 + }, + { + "year": 2008 + }, + { + "year": 2007 + }, + { + "year": 2006 + }, + { + "year": 2005 + } + ], + "salePriceIsEstimated": false + }, + "mortgageHistory": [ + { + "borrowers": [ + "BARNES DAVID" + ], + "saleDate": "2006-02-10T00:00:00.000Z", + "recordingDate": "2006-02-21T00:00:00.000Z", + "dueDate": "2021-02-15T00:00:00.000Z", + "lenderName": "GREENPOINT MORTGAGE FUNDING INC", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 42000, + "loanTermMonths": 180, + "interestRate": "6.15", + "transactionType": "HELOCS - both purchase money and non-purchase money HELOCS", + "transactionTypeCode": "F", + "documentDate": "2006-02-10T00:00:00.000Z" + }, + { + "borrowers": [ + "MADISON NATHAN S", + "MADISON MARY L" + ], + "saleDate": "2021-10-04T00:00:00.000Z", + "recordingDate": "2021-10-08T00:00:00.000Z", + "dueDate": "2051-11-01T00:00:00.000Z", + "lenderName": "ON Q FINANCIAL INC", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanTypeCode": "U", + "loanType": "New Conventional", + "loanAmount": 345000, + "loanTermMonths": 360, + "interestRate": "2.8", + "transactionType": "Refi loans and 2nd trust deeds (purchase and non-purchase money trust deeds)", + "transactionTypeCode": "G", + "documentDate": "2021-10-04T00:00:00.000Z" + } + ], + "openLien": { + "totalOpenLienCount": 1, + "mortgages": [ + { + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": 6.43, + "loanType": "New Conventional", + "subordinateLoanFlag": false, + "equityFlag": false, + "helocFlag": false, + "pudRider": false, + "condoRider": false, + "multiFamilyRider": false, + "assumable": false, + "privateLender": false, + "secondHomeRider": false, + "sellerCarryBack": false, + "balloonRider": false + } + ] + }, + "owner": { + "fullName": "4522 W RAVINA LLC", + "mailingAddress": { + "addressValidity": "Partially Valid", + "street": "3225 McLeod Dr # 777", + "city": "Las Vegas", + "county": "Clark", + "state": "NV", + "zip": "89121", + "zipPlus4": "2257", + "hash": "45ba83fc378f6c2ee5a655f19dd58860", + "deliveryPointCode": "256", + "dpvMatchCode": "S", + "dpvFootnotes": "AABBCC" + }, + "names": [ + { + "last": "4522 W RAVINA LLC", + "full": "4522 W RAVINA LLC" + } + ], + "mailingOptOut": false, + "hasLandline": true + }, + "permit": { + "permitCount": 2, + "earliestDate": "2018-06-14", + "latestDate": "2021-09-03", + "totalJobValue": 22750, + "allTags": [ + "hvac", + "electrical" + ], + "tags": { + "addition": false, + "adu": false, + "bathroom": false, + "battery": false, + "demolition": false, + "electrical": true, + "electricMeter": false, + "evCharger": false, + "fireSprinkler": false, + "gas": false, + "generator": false, + "grading": false, + "heatPump": false, + "hvac": true, + "inspectionPassed": true, + "kitchen": false, + "newConstruction": false, + "plumbing": false, + "poolAndHotTub": false, + "remodel": false, + "roofing": false, + "solar": false, + "waterHeater": false, + "windowDoor": false + } + }, + "propertyOwnerProfile": { + "averageYearBuilt": 1983, + "propertiesCount": 8614, + "mortgagesTotalBalance": 1647737268, + "mortgagesCount": 4325, + "mortgagesAverageBalance": 380980 + }, + "quickLists": { + "absenteeOwner": true, + "absenteeOwnerInState": false, + "absenteeOwnerOutOfState": true, + "activeListing": false, + "activeAuction": false, + "cashBuyer": true, + "corporateOwned": true, + "expiredListing": false, + "failedListing": true, + "freeAndClear": true, + "fixAndFlip": false, + "forSaleByOwner": false, + "highEquity": true, + "inherited": false, + "involuntaryLien": false, + "listedBelowMarketPrice": true, + "lowEquity": false, + "mailingAddressVacant": false, + "onMarket": false, + "outOfStateOwner": true, + "ownerOccupied": false, + "pendingListing": false, + "preforeclosure": false, + "recentlySold": false, + "samePropertyAndMailingAddress": false, + "seniorOwner": false, + "taxDefault": false, + "tiredLandlord": false, + "unknownEquity": false, + "vacant": false, + "vacantLot": false, + "hasHoa": true, + "hasHoaFees": true, + "canceledListing": false, + "noticeOfSale": false, + "noticeOfDefault": false, + "noticeOfLisPendens": false + }, + "sale": { + "priorSale": { + "mortgages": [ + { + "documentNumber": "20200732182", + "pageNumber": null, + "bookNumber": null, + "recordingDate": "2020-08-11T00:00:00.000Z", + "loanAmount": 311920, + "loanTypeCode": "U", + "loanType": "New Conventional", + "financingTypeCode": null, + "financingType": null, + "interestRate": 3.1, + "dueDate": "2050-09-01T00:00:00.000Z", + "lenderName": "ON Q FINANCIAL INC", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanTerm": "360" + } + ] + } + }, + "valuation": { + "estimatedValue": 567493, + "priceRangeMin": 482369, + "priceRangeMax": 652617, + "standardDeviation": 6, + "confidenceScore": 94, + "asOfDate": "2025-07-09T00:00:00.000Z", + "equityCurrentEstimatedBalance": 567493, + "ltv": 0, + "equityPercent": 100 + }, + "searchMeta": { + "resultCount": 1, + "resultsFound": 435641 + } +} diff --git a/modules/skipTraceV1/skipTraceV1.communication.iml.json b/modules/skipTraceV1/skipTraceV1.communication.iml.json new file mode 100644 index 0000000..8066319 --- /dev/null +++ b/modules/skipTraceV1/skipTraceV1.communication.iml.json @@ -0,0 +1,26 @@ +{ + "url": "/v1/property/skip-trace", + "method": "POST", + "body": { + "requests": [ + { + "propertyAddress": "{{if(parameters.findBy == 'address', parameters.propertyAddress, undefined)}}", + "apn": "{{if(parameters.findBy == 'apn', ifempty(parameters.apn, undefined), undefined)}}", + "countyFipsCode": "{{if(parameters.findBy == 'apn', ifempty(parameters.countyFipsCode, undefined), undefined)}}", + "county": "{{if(parameters.findBy == 'apn', ifempty(parameters.county, undefined), undefined)}}", + "state": "{{if(parameters.findBy == 'apn', ifempty(parameters.state, undefined), undefined)}}", + "name": "{{ifempty(parameters.name, undefined)}}", + "mailingAddress": "{{ifempty(parameters.mailingAddress, undefined)}}", + "requestId": "{{ifempty(parameters.requestId, undefined)}}" + } + ], + "options": { + "includeTCPABlacklistedPhones": "{{if(parameters.includeTcpaFlaggedPhones == true, true, false)}}", + "prioritizeMobilePhones": "{{ifempty(parameters.prioritizeMobilePhones, undefined)}}" + } + }, + "response": { + "iterate": "{{body.results.persons}}", + "output": "{{item}}" + } +} diff --git a/modules/skipTraceV1/skipTraceV1.interface.iml.json b/modules/skipTraceV1/skipTraceV1.interface.iml.json new file mode 100644 index 0000000..d3e8bcf --- /dev/null +++ b/modules/skipTraceV1/skipTraceV1.interface.iml.json @@ -0,0 +1,310 @@ +[ + { + "name": "bankruptcy", + "type": "collection", + "label": "Bankruptcy", + "spec": [] + }, + { + "name": "dnc", + "type": "collection", + "label": "Dnc", + "spec": [] + }, + { + "name": "emails", + "type": "array", + "label": "Emails", + "spec": [ + { + "name": "email", + "type": "text", + "label": "Email" + } + ] + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [] + }, + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + } + ] + }, + { + "name": "phoneNumbers", + "type": "array", + "label": "Phone numbers", + "spec": [ + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "carrier", + "type": "text", + "label": "Carrier" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "tested", + "type": "boolean", + "label": "Tested" + }, + { + "name": "reachable", + "type": "boolean", + "label": "Reachable" + }, + { + "name": "score", + "type": "number", + "label": "Score" + } + ] + }, + { + "name": "litigator", + "type": "boolean", + "label": "Litigator" + }, + { + "name": "propertyAddress", + "type": "collection", + "label": "Property address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + } + ] + }, + { + "name": "property", + "type": "collection", + "label": "Property", + "spec": [ + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + } + ] + }, + { + "name": "owner", + "type": "collection", + "label": "Owner", + "spec": [ + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + } + ] + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + } + ] + } + ] + } + ] + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "matched", + "type": "boolean", + "label": "Matched" + }, + { + "name": "error", + "type": "boolean", + "label": "Error" + } + ] + } +] diff --git a/modules/skipTraceV1/skipTraceV1.mappable-params.iml.json b/modules/skipTraceV1/skipTraceV1.mappable-params.iml.json new file mode 100644 index 0000000..816b77f --- /dev/null +++ b/modules/skipTraceV1/skipTraceV1.mappable-params.iml.json @@ -0,0 +1,129 @@ +[ + { + "name": "findBy", + "type": "select", + "label": "Find owner by", + "required": true, + "default": "address", + "options": [ + { + "label": "Property address", + "value": "address" + }, + { + "label": "APN", + "value": "apn" + } + ] + }, + { + "name": "propertyAddress", + "type": "collection", + "label": "Property address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State", + "help": "Two-letter state code, for example AZ." + }, + { + "name": "zip", + "type": "text", + "label": "ZIP code" + } + ] + }, + { + "name": "apn", + "type": "text", + "label": "APN" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County FIPS code" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State (for APN)" + }, + { + "name": "name", + "type": "collection", + "label": "Owner name (optional)", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First name" + }, + { + "name": "last", + "type": "text", + "label": "Last name" + } + ] + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Owner mailing address (optional)", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State", + "help": "Two-letter state code, for example AZ." + }, + { + "name": "zip", + "type": "text", + "label": "ZIP code" + } + ] + }, + { + "name": "requestId", + "type": "text", + "label": "Request ID" + }, + { + "name": "includeTcpaFlaggedPhones", + "type": "boolean", + "label": "Include phone numbers flagged as TCPA litigators", + "default": false, + "help": "Off by default: flagged numbers are removed. Turn on only if your compliance process allows it." + }, + { + "name": "prioritizeMobilePhones", + "type": "boolean", + "label": "Prioritize mobile phones" + } +] diff --git a/modules/skipTraceV1/skipTraceV1.samples.iml.json b/modules/skipTraceV1/skipTraceV1.samples.iml.json new file mode 100644 index 0000000..9ec8c8d --- /dev/null +++ b/modules/skipTraceV1/skipTraceV1.samples.iml.json @@ -0,0 +1,97 @@ +{ + "bankruptcy": {}, + "dnc": {}, + "emails": [ + { + "email": "johndoe@gmail.net" + } + ], + "mailingAddress": {}, + "name": { + "first": "john", + "last": "doe" + }, + "phoneNumbers": [ + { + "number": "1111111111", + "carrier": "OMNIPOINT COMMUNICATIONS- INC.", + "type": "Mobile", + "tested": true, + "reachable": true, + "score": 100 + }, + { + "number": "2222222222", + "carrier": "VERIZON NEW YORK- INC.", + "type": "Land Line", + "tested": false, + "reachable": false, + "score": 95 + }, + { + "number": "3333333333", + "carrier": "VERIZON NEW YORK- INC.", + "type": "Land Line", + "tested": true, + "reachable": true, + "score": 90 + }, + { + "number": "4444444444", + "carrier": "VERIZON NEW YORK- INC.", + "type": "Land Line", + "tested": true, + "reachable": true, + "score": 85 + } + ], + "litigator": false, + "propertyAddress": { + "houseNumber": "1011", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507", + "formattedStreet": "Rosegold St", + "streetNoUnit": "1011 Rosegold St", + "hash": "b7bd4cea51b26af459febfbf822c99e2" + }, + "property": { + "address": { + "houseNumber": "1011", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507", + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "formattedStreet": "Rosegold St", + "streetNoUnit": "1011 Rosegold St" + }, + "owner": { + "name": { + "first": "john", + "last": "doe" + }, + "mailingAddress": { + "houseNumber": "1011", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "formattedStreet": "Rosegold St", + "streetNoUnit": "1011 Rosegold St", + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "zipPlus4": "2507" + } + } + }, + "meta": { + "matched": true, + "error": false + } +} diff --git a/modules/skipTraceV3/skipTraceV3.communication.iml.json b/modules/skipTraceV3/skipTraceV3.communication.iml.json new file mode 100644 index 0000000..0dacbae --- /dev/null +++ b/modules/skipTraceV3/skipTraceV3.communication.iml.json @@ -0,0 +1,26 @@ +{ + "url": "/v3/property/skip-trace", + "method": "POST", + "body": { + "requests": [ + { + "propertyAddress": "{{if(parameters.findBy == 'address', parameters.propertyAddress, undefined)}}", + "apn": "{{if(parameters.findBy == 'apn', ifempty(parameters.apn, undefined), undefined)}}", + "countyFipsCode": "{{if(parameters.findBy == 'apn', ifempty(parameters.countyFipsCode, undefined), undefined)}}", + "county": "{{if(parameters.findBy == 'apn', ifempty(parameters.county, undefined), undefined)}}", + "state": "{{if(parameters.findBy == 'apn', ifempty(parameters.state, undefined), undefined)}}", + "name": "{{ifempty(parameters.name, undefined)}}", + "mailingAddress": "{{ifempty(parameters.mailingAddress, undefined)}}", + "requestId": "{{ifempty(parameters.requestId, undefined)}}" + } + ], + "options": { + "includeTCPABlacklistedPhones": "{{if(parameters.includeTcpaFlaggedPhones == true, true, false)}}", + "prioritizeMobilePhones": "{{ifempty(parameters.prioritizeMobilePhones, undefined)}}" + } + }, + "response": { + "iterate": "{{body.result.data}}", + "output": "{{item}}" + } +} diff --git a/modules/skipTraceV3/skipTraceV3.interface.iml.json b/modules/skipTraceV3/skipTraceV3.interface.iml.json new file mode 100644 index 0000000..5efaae9 --- /dev/null +++ b/modules/skipTraceV3/skipTraceV3.interface.iml.json @@ -0,0 +1,442 @@ +[ + { + "name": "input", + "type": "collection", + "label": "Input", + "spec": [ + { + "name": "propertyAddress", + "type": "collection", + "label": "Property address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + } + ] + } + ] + }, + { + "name": "property", + "type": "collection", + "label": "Property", + "spec": [ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "addressValidity", + "type": "text", + "label": "Address validity" + }, + { + "name": "fullAddress", + "type": "text", + "label": "Full address" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + } + ] + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [ + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "addressValidity", + "type": "text", + "label": "Address validity" + }, + { + "name": "fullAddress", + "type": "text", + "label": "Full address" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + } + ] + }, + { + "name": "owners", + "type": "array", + "label": "Owners", + "spec": [ + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + }, + { + "name": "akas", + "type": "array", + "label": "Akas", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "middle", + "type": "text", + "label": "Middle" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + }, + { + "name": "rank", + "type": "number", + "label": "Rank" + } + ] + } + ] + } + ] + } + ] + }, + { + "name": "persons", + "type": "array", + "label": "Persons", + "spec": [ + { + "name": "propertyOwner", + "type": "boolean", + "label": "Property owner" + }, + { + "name": "name", + "type": "collection", + "label": "Name", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + }, + { + "name": "akas", + "type": "array", + "label": "Akas", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "middle", + "type": "text", + "label": "Middle" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + }, + { + "name": "rank", + "type": "number", + "label": "Rank" + } + ] + } + ] + }, + { + "name": "addresses", + "type": "array", + "label": "Addresses", + "spec": [ + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "addressValidity", + "type": "text", + "label": "Address validity" + }, + { + "name": "fullAddress", + "type": "text", + "label": "Full address" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "rank", + "type": "number", + "label": "Rank" + }, + { + "name": "propertyMailingAddress", + "type": "boolean", + "label": "Property mailing address" + } + ] + }, + { + "name": "phones", + "type": "array", + "label": "Phones", + "spec": [ + { + "name": "rank", + "type": "number", + "label": "Rank" + }, + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "carrier", + "type": "text", + "label": "Carrier" + }, + { + "name": "tested", + "type": "boolean", + "label": "Tested" + }, + { + "name": "reachable", + "type": "boolean", + "label": "Reachable" + }, + { + "name": "dnc", + "type": "boolean", + "label": "Dnc" + }, + { + "name": "tcpa", + "type": "boolean", + "label": "Tcpa" + } + ] + }, + { + "name": "emails", + "type": "array", + "label": "Emails", + "spec": [ + { + "name": "rank", + "type": "number", + "label": "Rank" + }, + { + "name": "email", + "type": "text", + "label": "Email" + }, + { + "name": "tested", + "type": "boolean", + "label": "Tested" + } + ] + }, + { + "name": "litigator", + "type": "boolean", + "label": "Litigator" + }, + { + "name": "deceased", + "type": "boolean", + "label": "Deceased" + }, + { + "name": "dob", + "type": "text", + "label": "Dob" + } + ] + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "matched", + "type": "boolean", + "label": "Matched" + }, + { + "name": "error", + "type": "boolean", + "label": "Error" + } + ] + } +] diff --git a/modules/skipTraceV3/skipTraceV3.mappable-params.iml.json b/modules/skipTraceV3/skipTraceV3.mappable-params.iml.json new file mode 100644 index 0000000..816b77f --- /dev/null +++ b/modules/skipTraceV3/skipTraceV3.mappable-params.iml.json @@ -0,0 +1,129 @@ +[ + { + "name": "findBy", + "type": "select", + "label": "Find owner by", + "required": true, + "default": "address", + "options": [ + { + "label": "Property address", + "value": "address" + }, + { + "label": "APN", + "value": "apn" + } + ] + }, + { + "name": "propertyAddress", + "type": "collection", + "label": "Property address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State", + "help": "Two-letter state code, for example AZ." + }, + { + "name": "zip", + "type": "text", + "label": "ZIP code" + } + ] + }, + { + "name": "apn", + "type": "text", + "label": "APN" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County FIPS code" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State (for APN)" + }, + { + "name": "name", + "type": "collection", + "label": "Owner name (optional)", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First name" + }, + { + "name": "last", + "type": "text", + "label": "Last name" + } + ] + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Owner mailing address (optional)", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State", + "help": "Two-letter state code, for example AZ." + }, + { + "name": "zip", + "type": "text", + "label": "ZIP code" + } + ] + }, + { + "name": "requestId", + "type": "text", + "label": "Request ID" + }, + { + "name": "includeTcpaFlaggedPhones", + "type": "boolean", + "label": "Include phone numbers flagged as TCPA litigators", + "default": false, + "help": "Off by default: flagged numbers are removed. Turn on only if your compliance process allows it." + }, + { + "name": "prioritizeMobilePhones", + "type": "boolean", + "label": "Prioritize mobile phones" + } +] diff --git a/modules/skipTraceV3/skipTraceV3.samples.iml.json b/modules/skipTraceV3/skipTraceV3.samples.iml.json new file mode 100644 index 0000000..4f5f01c --- /dev/null +++ b/modules/skipTraceV3/skipTraceV3.samples.iml.json @@ -0,0 +1,168 @@ +{ + "input": { + "propertyAddress": { + "street": "1011 Rosegold St", + "city": "Franklin Square", + "state": "NY", + "zip": "11010" + } + }, + "property": { + "id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "address": { + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "addressValidity": "Valid", + "fullAddress": "1011 Rosegold St, Franklin Square, NY 11010-2507", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507" + }, + "mailingAddress": { + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "addressValidity": "Valid", + "fullAddress": "1011 Rosegold St, Franklin Square, NY 11010-2507", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507" + }, + "owners": [ + { + "name": { + "first": "John", + "last": "Doe", + "full": "John Doe", + "akas": [ + { + "first": "John", + "middle": "Quinn", + "last": "Doe", + "full": "John Quinn Doe", + "rank": 2 + }, + { + "first": "Jhon", + "last": "Doe", + "full": "Jhon Doe", + "rank": 3 + } + ] + } + } + ] + }, + "persons": [ + { + "propertyOwner": true, + "name": { + "first": "John", + "last": "Doe", + "full": "John Doe", + "akas": [ + { + "first": "John", + "middle": "Quinn", + "last": "Doe", + "full": "John Quinn Doe", + "rank": 2 + }, + { + "first": "Jhon", + "last": "Doe", + "full": "Jhon Doe", + "rank": 3 + } + ] + }, + "addresses": [ + { + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "addressValidity": "Valid", + "fullAddress": "1011 Rosegold St, Franklin Square, NY 11010-2507", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507", + "rank": 1, + "propertyMailingAddress": true + }, + { + "hash": "c8ce5dfa62c37bg560gfcgcg933da0f3", + "addressValidity": "Valid", + "fullAddress": "500 Maple Ave, Hempstead, NY 11550-3210", + "street": "500 Maple Ave", + "city": "Hempstead", + "county": "Nassau", + "state": "NY", + "zip": "11550", + "zipPlus4": "3210", + "rank": 2, + "propertyMailingAddress": false + } + ], + "phones": [ + { + "rank": 1, + "number": "1112223333", + "type": "Mobile", + "carrier": "OMNIPOINT COMMUNICATIONS- INC.", + "tested": true, + "reachable": true, + "dnc": false, + "tcpa": false + }, + { + "rank": 2, + "number": "4445556666", + "type": "Land Line", + "carrier": "VERIZON NEW YORK- INC.", + "tested": false, + "reachable": false, + "dnc": false, + "tcpa": false + }, + { + "rank": 3, + "number": "7778889999", + "type": "Land Line", + "carrier": "VERIZON NEW YORK- INC.", + "tested": true, + "reachable": true, + "dnc": true, + "tcpa": false + }, + { + "rank": 4, + "number": "1011121314", + "type": "Land Line", + "carrier": "VERIZON NEW YORK- INC.", + "tested": true, + "reachable": true, + "dnc": false, + "tcpa": true + } + ], + "emails": [ + { + "rank": 1, + "email": "johndoe@gmail.net", + "tested": true + } + ], + "litigator": false, + "deceased": false, + "dob": "1985-07-21T00:00:00.000Z" + } + ], + "meta": { + "matched": true, + "error": false + } +} diff --git a/modules/verifyAddress/verifyAddress.communication.iml.json b/modules/verifyAddress/verifyAddress.communication.iml.json new file mode 100644 index 0000000..02882e1 --- /dev/null +++ b/modules/verifyAddress/verifyAddress.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v1/address/verify", + "method": "POST", + "body": { + "requests": [ + { + "street": "{{parameters.street}}", + "city": "{{ifempty(parameters.city, undefined)}}", + "state": "{{ifempty(parameters.state, undefined)}}", + "zip": "{{ifempty(parameters.zip, undefined)}}" + } + ] + }, + "response": { + "output": "{{first(body.results.addresses)}}" + } +} diff --git a/modules/verifyAddress/verifyAddress.interface.iml.json b/modules/verifyAddress/verifyAddress.interface.iml.json new file mode 100644 index 0000000..ff876ff --- /dev/null +++ b/modules/verifyAddress/verifyAddress.interface.iml.json @@ -0,0 +1,147 @@ +[ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "geoLocation", + "type": "array", + "label": "Geo location", + "spec": { + "type": "number" + } + }, + { + "name": "requestId", + "type": "number", + "label": "Request id" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "error", + "type": "boolean", + "label": "Error" + }, + { + "name": "normalized", + "type": "boolean", + "label": "Normalized" + }, + { + "name": "hashed", + "type": "boolean", + "label": "Hashed" + } + ] + }, + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "vacant", + "type": "text", + "label": "Vacant" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County fips code" + }, + { + "name": "carrierRoute", + "type": "text", + "label": "Carrier route" + }, + { + "name": "congressionalDistrict", + "type": "text", + "label": "Congressional district" + }, + { + "name": "timeZone", + "type": "text", + "label": "Time zone" + }, + { + "name": "utcOffset", + "type": "number", + "label": "Utc offset" + }, + { + "name": "daylightSavingsTime", + "type": "boolean", + "label": "Daylight savings time" + }, + { + "name": "dpvMatchCode", + "type": "text", + "label": "Dpv match code" + }, + { + "name": "dpvFootnotes", + "type": "text", + "label": "Dpv footnotes" + }, + { + "name": "footnotes", + "type": "text", + "label": "Footnotes" + } +] diff --git a/modules/verifyAddress/verifyAddress.mappable-params.iml.json b/modules/verifyAddress/verifyAddress.mappable-params.iml.json new file mode 100644 index 0000000..3dcebd8 --- /dev/null +++ b/modules/verifyAddress/verifyAddress.mappable-params.iml.json @@ -0,0 +1,23 @@ +[ + { + "name": "street", + "type": "text", + "label": "Street", + "required": true + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "ZIP code" + } +] diff --git a/modules/verifyAddress/verifyAddress.samples.iml.json b/modules/verifyAddress/verifyAddress.samples.iml.json new file mode 100644 index 0000000..04f0714 --- /dev/null +++ b/modules/verifyAddress/verifyAddress.samples.iml.json @@ -0,0 +1,34 @@ +{ + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa", + "hash": "90a01b18e3834e349a8d1d07d7e086ba", + "latitude": 33.47865, + "longitude": -112.03029, + "geoLocation": [ + 33.47865, + -112.03029 + ], + "requestId": 0, + "formattedStreet": "N 24th St", + "streetNoUnit": "2800 N 24th St", + "meta": { + "error": false, + "normalized": true, + "hashed": true + }, + "houseNumber": "2800", + "zipPlus4": "1049", + "vacant": "No", + "countyFipsCode": "04013", + "carrierRoute": "C009", + "congressionalDistrict": "07", + "timeZone": "Mountain", + "utcOffset": -7, + "daylightSavingsTime": false, + "dpvMatchCode": "D", + "dpvFootnotes": "AAN1", + "footnotes": "B#H#" +} diff --git a/modules/verifyPhones/verifyPhones.communication.iml.json b/modules/verifyPhones/verifyPhones.communication.iml.json new file mode 100644 index 0000000..ba98370 --- /dev/null +++ b/modules/verifyPhones/verifyPhones.communication.iml.json @@ -0,0 +1,11 @@ +{ + "url": "/v1/phone/verification", + "method": "POST", + "body": { + "requests": "{{parameters.phoneNumbers}}" + }, + "response": { + "iterate": "{{body.results.phoneNumbers}}", + "output": "{{item}}" + } +} diff --git a/modules/verifyPhones/verifyPhones.interface.iml.json b/modules/verifyPhones/verifyPhones.interface.iml.json new file mode 100644 index 0000000..1a85ff8 --- /dev/null +++ b/modules/verifyPhones/verifyPhones.interface.iml.json @@ -0,0 +1,64 @@ +[ + { + "name": "carrier", + "type": "text", + "label": "Carrier" + }, + { + "name": "dnc", + "type": "boolean", + "label": "Dnc" + }, + { + "name": "notFound", + "type": "boolean", + "label": "Not found" + }, + { + "name": "number", + "type": "text", + "label": "Number" + }, + { + "name": "reachable", + "type": "boolean", + "label": "Reachable" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "tcpa", + "type": "boolean", + "label": "Tcpa" + }, + { + "name": "tested", + "type": "boolean", + "label": "Tested" + }, + { + "name": "type", + "type": "text", + "label": "Type" + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "error", + "type": "boolean", + "label": "Error" + }, + { + "name": "matched", + "type": "boolean", + "label": "Matched" + } + ] + } +] diff --git a/modules/verifyPhones/verifyPhones.mappable-params.iml.json b/modules/verifyPhones/verifyPhones.mappable-params.iml.json new file mode 100644 index 0000000..6ccea73 --- /dev/null +++ b/modules/verifyPhones/verifyPhones.mappable-params.iml.json @@ -0,0 +1,13 @@ +[ + { + "name": "phoneNumbers", + "type": "array", + "label": "Phone numbers", + "required": true, + "help": "One number per item, for example 4805550123. You are billed per phone number.", + "spec": { + "type": "text", + "label": "Phone number" + } + } +] diff --git a/modules/verifyPhones/verifyPhones.samples.iml.json b/modules/verifyPhones/verifyPhones.samples.iml.json new file mode 100644 index 0000000..12476ec --- /dev/null +++ b/modules/verifyPhones/verifyPhones.samples.iml.json @@ -0,0 +1,15 @@ +{ + "carrier": "OMNIPOINT COMMUNICATIONS- INC.", + "dnc": false, + "notFound": false, + "number": "6462467560", + "reachable": true, + "state": "NY", + "tcpa": false, + "tested": true, + "type": "Mobile", + "meta": { + "error": false, + "matched": true + } +} diff --git a/modules/watchMonitorEvents/watchMonitorEvents.communication.iml.json b/modules/watchMonitorEvents/watchMonitorEvents.communication.iml.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/modules/watchMonitorEvents/watchMonitorEvents.communication.iml.json @@ -0,0 +1 @@ +{} diff --git a/modules/watchMonitorEvents/watchMonitorEvents.interface.iml.json b/modules/watchMonitorEvents/watchMonitorEvents.interface.iml.json new file mode 100644 index 0000000..c4e6f23 --- /dev/null +++ b/modules/watchMonitorEvents/watchMonitorEvents.interface.iml.json @@ -0,0 +1,53 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "eventId", + "type": "text", + "label": "Event id" + }, + { + "name": "propertyId", + "type": "text", + "label": "Property id" + }, + { + "name": "parcelHash", + "type": "text", + "label": "Parcel hash" + }, + { + "name": "addressHash", + "type": "text", + "label": "Address hash" + }, + { + "name": "addedToSubscriptionIds", + "type": "array", + "label": "Added to subscription ids", + "spec": { + "type": "text" + } + }, + { + "name": "removedFromSubscriptionIds", + "type": "array", + "label": "Removed from subscription ids", + "spec": { + "type": "text" + } + }, + { + "name": "propertyChangedAt", + "type": "text", + "label": "Property changed at" + }, + { + "name": "sentAt", + "type": "text", + "label": "Sent at" + } +] diff --git a/modules/watchMonitorEvents/watchMonitorEvents.samples.iml.json b/modules/watchMonitorEvents/watchMonitorEvents.samples.iml.json new file mode 100644 index 0000000..755f690 --- /dev/null +++ b/modules/watchMonitorEvents/watchMonitorEvents.samples.iml.json @@ -0,0 +1,13 @@ +{ + "id": "evt_9c1f2a", + "eventId": "evt_9c1f2a", + "propertyId": "09201d3bd3134f9380704014781fb4c0", + "parcelHash": "a1b2c3d4e5f60718293a4b5c6d7e8f90", + "addressHash": "90a01b18e3834e349a8d1d07d7e086ba", + "addedToSubscriptionIds": [ + "FXeLSlemePGGhvt" + ], + "removedFromSubscriptionIds": [], + "propertyChangedAt": "2026-10-28T22:10:41.000Z", + "sentAt": "2026-10-28T22:10:45.000Z" +} diff --git a/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.communication.iml.json b/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.communication.iml.json new file mode 100644 index 0000000..73c1cbf --- /dev/null +++ b/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v1/property/lookup/all-attributes", + "method": "POST", + "body": { + "requests": [ + { + "propertyId": "{{payload.propertyId}}" + } + ] + }, + "response": { + "output": { + "{{...}}": "{{payload}}", + "property": "{{first(body.results.properties)}}" + } + } +} diff --git a/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.interface.iml.json b/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.interface.iml.json new file mode 100644 index 0000000..d0f1abb --- /dev/null +++ b/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.interface.iml.json @@ -0,0 +1,1747 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "eventId", + "type": "text", + "label": "Event id" + }, + { + "name": "propertyId", + "type": "text", + "label": "Property id" + }, + { + "name": "parcelHash", + "type": "text", + "label": "Parcel hash" + }, + { + "name": "addressHash", + "type": "text", + "label": "Address hash" + }, + { + "name": "addedToSubscriptionIds", + "type": "array", + "label": "Added to subscription ids", + "spec": { + "type": "text" + } + }, + { + "name": "removedFromSubscriptionIds", + "type": "array", + "label": "Removed from subscription ids", + "spec": { + "type": "text" + } + }, + { + "name": "propertyChangedAt", + "type": "text", + "label": "Property changed at" + }, + { + "name": "sentAt", + "type": "text", + "label": "Sent at" + }, + { + "name": "property", + "type": "collection", + "label": "Property", + "spec": [ + { + "name": "_id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "cityAliases", + "type": "array", + "label": "City aliases", + "spec": { + "type": "text" + } + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "localities", + "type": "array", + "label": "Localities", + "spec": { + "type": "text" + } + }, + { + "name": "latitude", + "type": "number", + "label": "Latitude" + }, + { + "name": "longitude", + "type": "number", + "label": "Longitude" + }, + { + "name": "countyFipsCode", + "type": "text", + "label": "County fips code" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + }, + { + "name": "normalized", + "type": "boolean", + "label": "Normalized" + }, + { + "name": "geoStatus", + "type": "text", + "label": "Geo status" + }, + { + "name": "geoStatusCode", + "type": "text", + "label": "Geo status code" + } + ] + }, + { + "name": "assessment", + "type": "collection", + "label": "Assessment", + "spec": [ + { + "name": "assessedImprovementValue", + "type": "number", + "label": "Assessed improvement value" + }, + { + "name": "assessedLandValue", + "type": "number", + "label": "Assessed land value" + }, + { + "name": "totalAssessedValue", + "type": "number", + "label": "Total assessed value" + }, + { + "name": "assessmentYear", + "type": "number", + "label": "Assessment year" + }, + { + "name": "improvementMarketValue", + "type": "number", + "label": "Improvement market value" + }, + { + "name": "landMarketValue", + "type": "number", + "label": "Land market value" + }, + { + "name": "totalMarketValue", + "type": "number", + "label": "Total market value" + }, + { + "name": "marketValueYear", + "type": "number", + "label": "Market value year" + } + ] + }, + { + "name": "building", + "type": "collection", + "label": "Building", + "spec": [ + { + "name": "totalBuildingAreaSquareFeet", + "type": "number", + "label": "Total building area square feet" + }, + { + "name": "totalBuildingAreaCode", + "type": "text", + "label": "Total building area code" + }, + { + "name": "totalBuildingAreaCodeDescription", + "type": "text", + "label": "Total building area code description" + }, + { + "name": "livingAreaSquareFeet", + "type": "number", + "label": "Living area square feet" + }, + { + "name": "grossBuildingAreaSquareFeet", + "type": "number", + "label": "Gross building area square feet" + }, + { + "name": "adjustedBuildingAreaSquareFeet", + "type": "number", + "label": "Adjusted building area square feet" + }, + { + "name": "yearBuilt", + "type": "number", + "label": "Year built" + }, + { + "name": "effectiveYearBuilt", + "type": "number", + "label": "Effective year built" + }, + { + "name": "buildingCount", + "type": "number", + "label": "Building count" + }, + { + "name": "roomCount", + "type": "number", + "label": "Room count" + }, + { + "name": "unitCount", + "type": "number", + "label": "Unit count" + }, + { + "name": "calculatedBathroomCount", + "type": "number", + "label": "Calculated bathroom count" + }, + { + "name": "fullBathroomCount", + "type": "number", + "label": "Full bathroom count" + }, + { + "name": "bathroomCount", + "type": "number", + "label": "Bathroom count" + }, + { + "name": "bathFixtureCount", + "type": "number", + "label": "Bath fixture count" + }, + { + "name": "storyCount", + "type": "number", + "label": "Story count" + }, + { + "name": "garageParkingSpaceCount", + "type": "number", + "label": "Garage parking space count" + }, + { + "name": "residentialUnitCount", + "type": "number", + "label": "Residential unit count" + }, + { + "name": "features", + "type": "array", + "label": "Features", + "spec": { + "type": "text" + } + }, + { + "name": "airConditioningSourceCode", + "type": "text", + "label": "Air conditioning source code" + }, + { + "name": "airConditioningSource", + "type": "text", + "label": "Air conditioning source" + }, + { + "name": "buildingConditionCode", + "type": "text", + "label": "Building condition code" + }, + { + "name": "buildingCondition", + "type": "text", + "label": "Building condition" + }, + { + "name": "buildingQualityCode", + "type": "text", + "label": "Building quality code" + }, + { + "name": "buildingQuality", + "type": "text", + "label": "Building quality" + }, + { + "name": "constructionTypeCode", + "type": "text", + "label": "Construction type code" + }, + { + "name": "constructionType", + "type": "text", + "label": "Construction type" + }, + { + "name": "exteriorWallsCode", + "type": "text", + "label": "Exterior walls code" + }, + { + "name": "exteriorWalls", + "type": "text", + "label": "Exterior walls" + }, + { + "name": "garageCode", + "type": "text", + "label": "Garage code" + }, + { + "name": "garage", + "type": "text", + "label": "Garage" + }, + { + "name": "heatSourceCode", + "type": "text", + "label": "Heat source code" + }, + { + "name": "heatSource", + "type": "text", + "label": "Heat source" + }, + { + "name": "patioCode", + "type": "text", + "label": "Patio code" + }, + { + "name": "patio", + "type": "text", + "label": "Patio" + }, + { + "name": "poolCode", + "type": "text", + "label": "Pool code" + }, + { + "name": "pool", + "type": "text", + "label": "Pool" + }, + { + "name": "roofCoverCode", + "type": "text", + "label": "Roof cover code" + }, + { + "name": "roofCover", + "type": "text", + "label": "Roof cover" + }, + { + "name": "fireplaceCount", + "type": "number", + "label": "Fireplace count" + } + ] + }, + { + "name": "deedHistory", + "type": "array", + "label": "Deed history", + "spec": [ + { + "name": "buyers", + "type": "array", + "label": "Buyers", + "spec": { + "type": "text" + } + }, + { + "name": "sellers", + "type": "array", + "label": "Sellers", + "spec": { + "type": "text" + } + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + }, + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentTypeCode", + "type": "text", + "label": "Document type code" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "salePrice", + "type": "number", + "label": "Sale price" + }, + { + "name": "salePriceCode", + "type": "text", + "label": "Sale price code" + }, + { + "name": "transferTax", + "type": "number", + "label": "Transfer tax" + }, + { + "name": "ownerRelationshipType", + "type": "text", + "label": "Owner relationship type" + }, + { + "name": "multipleOwners", + "type": "boolean", + "label": "Multiple owners" + } + ] + }, + { + "name": "demographics", + "type": "collection", + "label": "Demographics", + "spec": [ + { + "name": "age", + "type": "number", + "label": "Age" + }, + { + "name": "emailDomain", + "type": "text", + "label": "Email domain" + }, + { + "name": "householdSize", + "type": "number", + "label": "Household size" + }, + { + "name": "income", + "type": "number", + "label": "Income" + }, + { + "name": "netWorth", + "type": "number", + "label": "Net worth" + }, + { + "name": "discretionaryIncome", + "type": "number", + "label": "Discretionary income" + }, + { + "name": "homeownerRenterCode", + "type": "text", + "label": "Homeowner renter code" + }, + { + "name": "homeownerRenter", + "type": "text", + "label": "Homeowner renter" + }, + { + "name": "genderCode", + "type": "text", + "label": "Gender code" + }, + { + "name": "gender", + "type": "text", + "label": "Gender" + }, + { + "name": "maritalStatusCode", + "type": "text", + "label": "Marital status code" + }, + { + "name": "maritalStatus", + "type": "text", + "label": "Marital status" + }, + { + "name": "childCount", + "type": "number", + "label": "Child count" + }, + { + "name": "hasChildren", + "type": "boolean", + "label": "Has children" + }, + { + "name": "investments", + "type": "array", + "label": "Investments", + "spec": { + "type": "text" + } + }, + { + "name": "individualEducationCode", + "type": "text", + "label": "Individual education code" + }, + { + "name": "individualEducation", + "type": "text", + "label": "Individual education" + }, + { + "name": "individualOccupationCode", + "type": "text", + "label": "Individual occupation code" + }, + { + "name": "individualOccupation", + "type": "text", + "label": "Individual occupation" + }, + { + "name": "businessOwnerCode", + "type": "text", + "label": "Business owner code" + }, + { + "name": "businessOwner", + "type": "text", + "label": "Business owner" + }, + { + "name": "petOwner", + "type": "boolean", + "label": "Pet owner" + }, + { + "name": "millionaire", + "type": "boolean", + "label": "Millionaire" + }, + { + "name": "recentlyDivorced", + "type": "boolean", + "label": "Recently divorced" + }, + { + "name": "singleParent", + "type": "boolean", + "label": "Single parent" + }, + { + "name": "smoker", + "type": "boolean", + "label": "Smoker" + }, + { + "name": "religious", + "type": "boolean", + "label": "Religious" + }, + { + "name": "religiousAffiliationCode", + "type": "text", + "label": "Religious affiliation code" + }, + { + "name": "religiousAffiliation", + "type": "text", + "label": "Religious affiliation" + }, + { + "name": "recentlyMoved", + "type": "boolean", + "label": "Recently moved" + }, + { + "name": "estimatedAreaCreditRating", + "type": "text", + "label": "Estimated area credit rating" + } + ] + }, + { + "name": "foreclosure", + "type": "collection", + "label": "Foreclosure", + "spec": [] + }, + { + "name": "permit", + "type": "collection", + "label": "Permit", + "spec": [ + { + "name": "permitCount", + "type": "number", + "label": "Permit count" + }, + { + "name": "earliestDate", + "type": "text", + "label": "Earliest date" + }, + { + "name": "latestDate", + "type": "text", + "label": "Latest date" + }, + { + "name": "totalJobValue", + "type": "number", + "label": "Total job value" + }, + { + "name": "allTags", + "type": "array", + "label": "All tags", + "spec": { + "type": "text" + } + }, + { + "name": "tags", + "type": "collection", + "label": "Tags", + "spec": [ + { + "name": "addition", + "type": "boolean", + "label": "Addition" + }, + { + "name": "adu", + "type": "boolean", + "label": "Adu" + }, + { + "name": "bathroom", + "type": "boolean", + "label": "Bathroom" + }, + { + "name": "battery", + "type": "boolean", + "label": "Battery" + }, + { + "name": "demolition", + "type": "boolean", + "label": "Demolition" + }, + { + "name": "electrical", + "type": "boolean", + "label": "Electrical" + }, + { + "name": "electricMeter", + "type": "boolean", + "label": "Electric meter" + }, + { + "name": "evCharger", + "type": "boolean", + "label": "Ev charger" + }, + { + "name": "fireSprinkler", + "type": "boolean", + "label": "Fire sprinkler" + }, + { + "name": "gas", + "type": "boolean", + "label": "Gas" + }, + { + "name": "generator", + "type": "boolean", + "label": "Generator" + }, + { + "name": "grading", + "type": "boolean", + "label": "Grading" + }, + { + "name": "heatPump", + "type": "boolean", + "label": "Heat pump" + }, + { + "name": "hvac", + "type": "boolean", + "label": "Hvac" + }, + { + "name": "inspectionPassed", + "type": "boolean", + "label": "Inspection passed" + }, + { + "name": "kitchen", + "type": "boolean", + "label": "Kitchen" + }, + { + "name": "newConstruction", + "type": "boolean", + "label": "New construction" + }, + { + "name": "plumbing", + "type": "boolean", + "label": "Plumbing" + }, + { + "name": "poolAndHotTub", + "type": "boolean", + "label": "Pool and hot tub" + }, + { + "name": "remodel", + "type": "boolean", + "label": "Remodel" + }, + { + "name": "roofing", + "type": "boolean", + "label": "Roofing" + }, + { + "name": "solar", + "type": "boolean", + "label": "Solar" + }, + { + "name": "waterHeater", + "type": "boolean", + "label": "Water heater" + }, + { + "name": "windowDoor", + "type": "boolean", + "label": "Window door" + } + ] + } + ] + }, + { + "name": "general", + "type": "collection", + "label": "General", + "spec": [ + { + "name": "carrierRoute", + "type": "text", + "label": "Carrier route" + }, + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + }, + { + "name": "mailingAddressVacant", + "type": "boolean", + "label": "Mailing address vacant" + }, + { + "name": "vacantDate", + "type": "text", + "label": "Vacant date" + }, + { + "name": "mobileHome", + "type": "boolean", + "label": "Mobile home" + }, + { + "name": "standardizedLandUseCode", + "type": "text", + "label": "Standardized land use code" + }, + { + "name": "propertyTypeCategory", + "type": "text", + "label": "Property type category" + }, + { + "name": "propertyTypeDetail", + "type": "text", + "label": "Property type detail" + }, + { + "name": "congressionalDistrict", + "type": "text", + "label": "Congressional district" + }, + { + "name": "timeZone", + "type": "text", + "label": "Time zone" + }, + { + "name": "utcOffset", + "type": "number", + "label": "Utc offset" + }, + { + "name": "daylightSavingsTime", + "type": "boolean", + "label": "Daylight savings time" + }, + { + "name": "censusTract", + "type": "text", + "label": "Census tract" + }, + { + "name": "primaryParcel", + "type": "boolean", + "label": "Primary parcel" + }, + { + "name": "parcelCount", + "type": "number", + "label": "Parcel count" + } + ] + }, + { + "name": "ids", + "type": "collection", + "label": "Ids", + "spec": [ + { + "name": "addressHash", + "type": "text", + "label": "Address hash" + }, + { + "name": "apn", + "type": "text", + "label": "Apn" + }, + { + "name": "oldApn", + "type": "text", + "label": "Old apn" + }, + { + "name": "fipsCode", + "type": "text", + "label": "Fips code" + } + ] + }, + { + "name": "legal", + "type": "collection", + "label": "Legal", + "spec": [ + { + "name": "assessorsMapReference", + "type": "text", + "label": "Assessors map reference" + }, + { + "name": "cityTownshipMunicipality", + "type": "text", + "label": "City township municipality" + }, + { + "name": "legalDescription", + "type": "text", + "label": "Legal description" + }, + { + "name": "lotCode", + "type": "text", + "label": "Lot code" + }, + { + "name": "lotNumber", + "type": "text", + "label": "Lot number" + }, + { + "name": "sectionTownshipRangeMeridian", + "type": "text", + "label": "Section township range meridian" + }, + { + "name": "subdivisionName", + "type": "text", + "label": "Subdivision name" + }, + { + "name": "tractNumber", + "type": "text", + "label": "Tract number" + } + ] + }, + { + "name": "lot", + "type": "collection", + "label": "Lot", + "spec": [ + { + "name": "lotSizeAcres", + "type": "number", + "label": "Lot size acres" + }, + { + "name": "lotSizeSquareFeet", + "type": "number", + "label": "Lot size square feet" + }, + { + "name": "zoningCode", + "type": "text", + "label": "Zoning code" + } + ] + }, + { + "name": "mortgageHistory", + "type": "array", + "label": "Mortgage history", + "spec": [ + { + "name": "borrowers", + "type": "array", + "label": "Borrowers", + "spec": { + "type": "text" + } + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "dueDate", + "type": "text", + "label": "Due date" + }, + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "lenderTypeCode", + "type": "text", + "label": "Lender type code" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTermMonths", + "type": "number", + "label": "Loan term months" + }, + { + "name": "interestRate", + "type": "text", + "label": "Interest rate" + } + ] + }, + { + "name": "openLien", + "type": "collection", + "label": "Open lien", + "spec": [ + { + "name": "allLoanTypes", + "type": "array", + "label": "All loan types", + "spec": { + "type": "text" + } + }, + { + "name": "juniorLoanTypes", + "type": "array", + "label": "Junior loan types", + "spec": { + "type": "text" + } + }, + { + "name": "totalOpenLienCount", + "type": "number", + "label": "Total open lien count" + }, + { + "name": "mortgages", + "type": "array", + "label": "Mortgages", + "spec": [ + { + "name": "lenderName", + "type": "text", + "label": "Lender name" + }, + { + "name": "lenderType", + "type": "text", + "label": "Lender type" + }, + { + "name": "loanAmount", + "type": "number", + "label": "Loan amount" + }, + { + "name": "loanTermMonths", + "type": "number", + "label": "Loan term months" + }, + { + "name": "interestRate", + "type": "number", + "label": "Interest rate" + }, + { + "name": "loanType", + "type": "text", + "label": "Loan type" + }, + { + "name": "subordinateLoanFlag", + "type": "boolean", + "label": "Subordinate loan flag" + }, + { + "name": "equityFlag", + "type": "boolean", + "label": "Equity flag" + }, + { + "name": "helocFlag", + "type": "boolean", + "label": "Heloc flag" + }, + { + "name": "pudRider", + "type": "boolean", + "label": "Pud rider" + }, + { + "name": "condoRider", + "type": "boolean", + "label": "Condo rider" + }, + { + "name": "multiFamilyRider", + "type": "boolean", + "label": "Multi family rider" + }, + { + "name": "assumable", + "type": "boolean", + "label": "Assumable" + }, + { + "name": "privateLender", + "type": "boolean", + "label": "Private lender" + }, + { + "name": "secondHomeRider", + "type": "boolean", + "label": "Second home rider" + }, + { + "name": "sellerCarryBack", + "type": "boolean", + "label": "Seller carry back" + }, + { + "name": "balloonRider", + "type": "boolean", + "label": "Balloon rider" + } + ] + } + ] + }, + { + "name": "owner", + "type": "collection", + "label": "Owner", + "spec": [ + { + "name": "fullName", + "type": "text", + "label": "Full name" + }, + { + "name": "mailingAddress", + "type": "collection", + "label": "Mailing address", + "spec": [ + { + "name": "houseNumber", + "type": "text", + "label": "House number" + }, + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "county", + "type": "text", + "label": "County" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "zipPlus4", + "type": "text", + "label": "Zip plus4" + }, + { + "name": "formattedStreet", + "type": "text", + "label": "Formatted street" + }, + { + "name": "streetNoUnit", + "type": "text", + "label": "Street no unit" + }, + { + "name": "hash", + "type": "text", + "label": "Hash" + } + ] + }, + { + "name": "names", + "type": "array", + "label": "Names", + "spec": [ + { + "name": "first", + "type": "text", + "label": "First" + }, + { + "name": "last", + "type": "text", + "label": "Last" + }, + { + "name": "full", + "type": "text", + "label": "Full" + } + ] + }, + { + "name": "ownerOccupied", + "type": "boolean", + "label": "Owner occupied" + }, + { + "name": "mailingOptOut", + "type": "boolean", + "label": "Mailing opt out" + }, + { + "name": "hasLandline", + "type": "boolean", + "label": "Has landline" + }, + { + "name": "ownerStatusTypeCode", + "type": "text", + "label": "Owner status type code" + }, + { + "name": "ownerStatusType", + "type": "text", + "label": "Owner status type" + }, + { + "name": "ownershipRightsCode", + "type": "text", + "label": "Ownership rights code" + }, + { + "name": "ownershipRights", + "type": "text", + "label": "Ownership rights" + } + ] + }, + { + "name": "propertyOwnerProfile", + "type": "collection", + "label": "Property owner profile", + "spec": [ + { + "name": "averageYearBuilt", + "type": "number", + "label": "Average year built" + }, + { + "name": "propertiesCount", + "type": "number", + "label": "Properties count" + }, + { + "name": "propertiesTotalEquity", + "type": "number", + "label": "Properties total equity" + }, + { + "name": "propertiesTotalEstimatedValue", + "type": "number", + "label": "Properties total estimated value" + } + ] + }, + { + "name": "quickLists", + "type": "collection", + "label": "Quick lists", + "spec": [ + { + "name": "absenteeOwner", + "type": "boolean", + "label": "Absentee owner" + }, + { + "name": "absenteeOwnerInState", + "type": "boolean", + "label": "Absentee owner in state" + }, + { + "name": "absenteeOwnerOutOfState", + "type": "boolean", + "label": "Absentee owner out of state" + }, + { + "name": "activeListing", + "type": "boolean", + "label": "Active listing" + }, + { + "name": "activeAuction", + "type": "boolean", + "label": "Active auction" + }, + { + "name": "cashBuyer", + "type": "boolean", + "label": "Cash buyer" + }, + { + "name": "corporateOwned", + "type": "boolean", + "label": "Corporate owned" + }, + { + "name": "expiredListing", + "type": "boolean", + "label": "Expired listing" + }, + { + "name": "freeAndClear", + "type": "boolean", + "label": "Free and clear" + }, + { + "name": "highEquity", + "type": "boolean", + "label": "High equity" + }, + { + "name": "inherited", + "type": "boolean", + "label": "Inherited" + }, + { + "name": "listedBelowMarketPrice", + "type": "boolean", + "label": "Listed below market price" + }, + { + "name": "lowEquity", + "type": "boolean", + "label": "Low equity" + }, + { + "name": "mailingAddressVacant", + "type": "boolean", + "label": "Mailing address vacant" + }, + { + "name": "onMarket", + "type": "boolean", + "label": "On market" + }, + { + "name": "outOfStateOwner", + "type": "boolean", + "label": "Out of state owner" + }, + { + "name": "ownerOccupied", + "type": "boolean", + "label": "Owner occupied" + }, + { + "name": "pendingListing", + "type": "boolean", + "label": "Pending listing" + }, + { + "name": "preforeclosure", + "type": "boolean", + "label": "Preforeclosure" + }, + { + "name": "recentlySold", + "type": "boolean", + "label": "Recently sold" + }, + { + "name": "samePropertyAndMailingAddress", + "type": "boolean", + "label": "Same property and mailing address" + }, + { + "name": "taxDefault", + "type": "boolean", + "label": "Tax default" + }, + { + "name": "tiredLandlord", + "type": "boolean", + "label": "Tired landlord" + }, + { + "name": "unknownEquity", + "type": "boolean", + "label": "Unknown equity" + }, + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + }, + { + "name": "hasHoa", + "type": "boolean", + "label": "Has hoa" + }, + { + "name": "hasHoaFees", + "type": "boolean", + "label": "Has hoa fees" + }, + { + "name": "canceledListing", + "type": "boolean", + "label": "Canceled listing" + }, + { + "name": "noticeOfSale", + "type": "boolean", + "label": "Notice of sale" + }, + { + "name": "noticeOfDefault", + "type": "boolean", + "label": "Notice of default" + }, + { + "name": "noticeOfLisPendens", + "type": "boolean", + "label": "Notice of lis pendens" + } + ] + }, + { + "name": "sale", + "type": "collection", + "label": "Sale", + "spec": [ + { + "name": "lastTransfer", + "type": "collection", + "label": "Last transfer", + "spec": [ + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "priceCodeDescription", + "type": "text", + "label": "Price code description" + }, + { + "name": "priceCode", + "type": "text", + "label": "Price code" + }, + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleDate", + "type": "text", + "label": "Sale date" + } + ] + }, + { + "name": "lastSale", + "type": "collection", + "label": "Last sale", + "spec": [ + { + "name": "saleBuyers", + "type": "array", + "label": "Sale buyers", + "spec": { + "type": "text" + } + }, + { + "name": "saleSellers", + "type": "array", + "label": "Sale sellers", + "spec": { + "type": "text" + } + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + }, + { + "name": "flipLength", + "type": "number", + "label": "Flip length" + }, + { + "name": "flipLengthCategory", + "type": "number", + "label": "Flip length category" + }, + { + "name": "flipProfit", + "type": "number", + "label": "Flip profit" + }, + { + "name": "priorTransfer", + "type": "collection", + "label": "Prior transfer", + "spec": [ + { + "name": "documentNumber", + "type": "text", + "label": "Document number" + }, + { + "name": "documentType", + "type": "text", + "label": "Document type" + }, + { + "name": "priceCodeDescription", + "type": "text", + "label": "Price code description" + }, + { + "name": "priceCode", + "type": "text", + "label": "Price code" + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + }, + { + "name": "priorSale", + "type": "collection", + "label": "Prior sale", + "spec": [ + { + "name": "recordingDate", + "type": "text", + "label": "Recording date" + }, + { + "name": "saleBuyers", + "type": "array", + "label": "Sale buyers", + "spec": { + "type": "text" + } + }, + { + "name": "saleSellers", + "type": "array", + "label": "Sale sellers", + "spec": { + "type": "text" + } + }, + { + "name": "salePriceIsEstimated", + "type": "boolean", + "label": "Sale price is estimated" + } + ] + } + ] + }, + { + "name": "tax", + "type": "collection", + "label": "Tax", + "spec": [ + { + "name": "taxAmount", + "type": "number", + "label": "Tax amount" + }, + { + "name": "taxYear", + "type": "number", + "label": "Tax year" + }, + { + "name": "taxDelinquentYear", + "type": "number", + "label": "Tax delinquent year" + }, + { + "name": "taxRateCodeArea", + "type": "text", + "label": "Tax rate code area" + } + ] + }, + { + "name": "valuation", + "type": "collection", + "label": "Valuation", + "spec": [ + { + "name": "estimatedValue", + "type": "number", + "label": "Estimated value" + }, + { + "name": "priceRangeMin", + "type": "number", + "label": "Price range min" + }, + { + "name": "priceRangeMax", + "type": "number", + "label": "Price range max" + }, + { + "name": "standardDeviation", + "type": "number", + "label": "Standard deviation" + }, + { + "name": "confidenceScore", + "type": "number", + "label": "Confidence score" + }, + { + "name": "asOfDate", + "type": "text", + "label": "As of date" + }, + { + "name": "equityCurrentEstimatedBalance", + "type": "number", + "label": "Equity current estimated balance" + }, + { + "name": "ltv", + "type": "number", + "label": "Ltv" + }, + { + "name": "equityPercent", + "type": "number", + "label": "Equity percent" + } + ] + }, + { + "name": "meta", + "type": "collection", + "label": "Meta", + "spec": [ + { + "name": "propertyDateModified", + "type": "text", + "label": "Property date modified" + } + ] + } + ] + } +] diff --git a/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.samples.iml.json b/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.samples.iml.json new file mode 100644 index 0000000..aac0ae5 --- /dev/null +++ b/modules/watchMonitorEventsWithProperty/watchMonitorEventsWithProperty.samples.iml.json @@ -0,0 +1,550 @@ +{ + "id": "evt_9c1f2a", + "eventId": "evt_9c1f2a", + "propertyId": "09201d3bd3134f9380704014781fb4c0", + "parcelHash": "a1b2c3d4e5f60718293a4b5c6d7e8f90", + "addressHash": "90a01b18e3834e349a8d1d07d7e086ba", + "addedToSubscriptionIds": [ + "FXeLSlemePGGhvt" + ], + "removedFromSubscriptionIds": [], + "propertyChangedAt": "2026-10-28T22:10:41.000Z", + "sentAt": "2026-10-28T22:10:45.000Z", + "property": { + "_id": "76ef71f2bc0c999e87592cd8302c5d32", + "address": { + "houseNumber": "26823", + "street": "26823 N 31st Dr", + "city": "Phoenix", + "cityAliases": [ + "phoenix" + ], + "county": "Maricopa", + "state": "AZ", + "zip": "85083", + "zipPlus4": "5837", + "formattedStreet": "N 31st Dr", + "streetNoUnit": "26823 N 31st Dr", + "localities": [ + "arizona hillcrest" + ], + "latitude": 33.728785, + "longitude": -112.126019, + "countyFipsCode": "04013", + "hash": "fae16e6adfaf0bb0f306ba89bf7b4c1e", + "normalized": true, + "geoStatus": "Rooftop", + "geoStatusCode": "A" + }, + "assessment": { + "assessedImprovementValue": 27730, + "assessedLandValue": 6930, + "totalAssessedValue": 43300, + "assessmentYear": 2024, + "improvementMarketValue": 346400, + "landMarketValue": 86600, + "totalMarketValue": 433000, + "marketValueYear": 2024 + }, + "building": { + "totalBuildingAreaSquareFeet": 1761, + "totalBuildingAreaCode": "B", + "totalBuildingAreaCodeDescription": "Living Area", + "livingAreaSquareFeet": 1761, + "grossBuildingAreaSquareFeet": 2480, + "adjustedBuildingAreaSquareFeet": 2100, + "yearBuilt": 2001, + "effectiveYearBuilt": 2001, + "buildingCount": 1, + "roomCount": 6, + "unitCount": 1, + "calculatedBathroomCount": 2, + "fullBathroomCount": 2, + "bathroomCount": 2, + "bathFixtureCount": 7, + "storyCount": 1, + "garageParkingSpaceCount": 2, + "residentialUnitCount": 1, + "features": [ + "Air Conditioning", + "Refrigeration Air Conditioning", + "Sauna/Steam Room", + "Koi Pond", + "Mobile Home Hookup", + "Patio", + "Garage", + "Pool", + "Elevator" + ], + "airConditioningSourceCode": "J", + "airConditioningSource": "Refrigeration", + "buildingConditionCode": "F", + "buildingCondition": "Average", + "buildingQualityCode": "D", + "buildingQuality": "B", + "constructionTypeCode": "C", + "constructionType": "Frame", + "exteriorWallsCode": "S", + "exteriorWalls": "Stucco", + "garageCode": "I", + "garage": "Garage", + "heatSourceCode": "X", + "heatSource": "Yes", + "patioCode": "A", + "patio": "Patio", + "poolCode": "H", + "pool": "Pool - Yes", + "roofCoverCode": "I", + "roofCover": "Concrete", + "fireplaceCount": 0 + }, + "deedHistory": [ + { + "buyers": [ + "KB HOME SALES PHOENIX INC" + ], + "sellers": [ + "KB HOME PHOENIX INC", + "KAUFMAN & BROAD OF ARIZONA INC" + ], + "recordingDate": "2001-07-30T00:00:00.000Z", + "saleDate": "2001-07-20T00:00:00.000Z", + "documentNumber": "2001-0688814", + "documentTypeCode": "Z", + "documentType": "Corporation Deed", + "salePrice": 0, + "salePriceCode": "Full Consideration", + "transferTax": 412, + "ownerRelationshipType": "Arm's Length", + "multipleOwners": false + }, + { + "buyers": [ + "CHENEY RHONDA" + ], + "sellers": [ + "NUNEZ JR ROBERTO" + ], + "recordingDate": "2002-08-30T00:00:00.000Z", + "saleDate": "2002-08-19T00:00:00.000Z", + "documentNumber": "2002-0890956", + "documentTypeCode": "BF", + "documentType": "Quit Claim Deed", + "salePrice": 0 + }, + { + "buyers": [ + "ANDERSON RHONDA K" + ], + "sellers": [ + "ANDERSON KYLE DOUGLAS" + ], + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleDate": "2005-01-14T00:00:00.000Z", + "documentNumber": "2005-0080877", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + }, + { + "buyers": [ + "ANDERSON RHONDA K" + ], + "sellers": [ + "ANDERSON RHONDA K", + "CHENEY RHONDA" + ], + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleDate": "2005-01-14T00:00:00.000Z", + "documentNumber": "2005-0080876", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + }, + { + "buyers": [ + "ANDERSON KYLE", + "ANDERSON RHONDA K" + ], + "sellers": [], + "recordingDate": "2006-12-29T00:00:00.000Z", + "saleDate": "2006-12-11T00:00:00.000Z", + "documentNumber": "2006-1700576", + "documentTypeCode": "DN", + "documentType": "Stand Alone Mortgage" + }, + { + "buyers": [ + "ANDRESON RHONDA K", + "ANDERSON KYLE" + ], + "sellers": [ + "ANDRESON RHONDA K", + "ANDRESON KYLE" + ], + "recordingDate": "2013-01-18T00:00:00.000Z", + "saleDate": "2012-11-13T00:00:00.000Z", + "documentNumber": "2013-0056003", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + } + ], + "demographics": { + "age": 52, + "emailDomain": "gmail.com", + "householdSize": 3, + "income": 95000, + "netWorth": 425000, + "discretionaryIncome": 38000, + "homeownerRenterCode": "O", + "homeownerRenter": "Home Owner", + "genderCode": "M", + "gender": "Male", + "maritalStatusCode": "M", + "maritalStatus": "Married", + "childCount": 2, + "hasChildren": true, + "investments": [ + "Real Estate", + "Personal" + ], + "individualEducationCode": "BA", + "individualEducation": "Bachelor's Degree", + "individualOccupationCode": "PRO", + "individualOccupation": "Professional", + "businessOwnerCode": "A", + "businessOwner": "Real Estate", + "petOwner": true, + "millionaire": false, + "recentlyDivorced": false, + "singleParent": false, + "smoker": false, + "religious": true, + "religiousAffiliationCode": "C", + "religiousAffiliation": "Catholic", + "recentlyMoved": false, + "estimatedAreaCreditRating": "B" + }, + "foreclosure": {}, + "permit": { + "permitCount": 3, + "earliestDate": "2015-04-10", + "latestDate": "2022-08-22", + "totalJobValue": 48500, + "allTags": [ + "roofing", + "solar", + "electrical" + ], + "tags": { + "addition": false, + "adu": false, + "bathroom": false, + "battery": false, + "demolition": false, + "electrical": true, + "electricMeter": false, + "evCharger": false, + "fireSprinkler": false, + "gas": false, + "generator": false, + "grading": false, + "heatPump": false, + "hvac": false, + "inspectionPassed": true, + "kitchen": false, + "newConstruction": false, + "plumbing": false, + "poolAndHotTub": false, + "remodel": false, + "roofing": true, + "solar": true, + "waterHeater": false, + "windowDoor": false + } + }, + "general": { + "carrierRoute": "R087", + "vacant": false, + "mailingAddressVacant": false, + "vacantDate": "2021-05-14", + "mobileHome": false, + "standardizedLandUseCode": "R2", + "propertyTypeCategory": "Residential", + "propertyTypeDetail": "Single Family", + "congressionalDistrict": "8", + "timeZone": "Mountain", + "utcOffset": -7, + "daylightSavingsTime": false, + "censusTract": "040136119.001008", + "primaryParcel": true, + "parcelCount": 1 + }, + "ids": { + "addressHash": "fae16e6adfaf0bb0f306ba89bf7b4c1e", + "apn": "205-03-224", + "oldApn": "205032242", + "fipsCode": "04013" + }, + "legal": { + "assessorsMapReference": "MB 51824", + "cityTownshipMunicipality": "Phoenix", + "legalDescription": "ARIZONA HILLCREST MCR 518-24", + "lotCode": "S", + "lotNumber": "199", + "sectionTownshipRangeMeridian": "SEC 35 TWN 5N RNG 2E", + "subdivisionName": "ARIZONA HILLCREST", + "tractNumber": "ARIZONA HILLCREST" + }, + "lot": { + "lotSizeAcres": 0.14637281910009184, + "lotSizeSquareFeet": 6376, + "zoningCode": "R-8" + }, + "mortgageHistory": [ + { + "borrowers": [ + "CHENEY RHONDA" + ], + "saleDate": "2002-08-19T00:00:00.000Z", + "recordingDate": "2002-08-30T00:00:00.000Z", + "dueDate": "2032-09-01T00:00:00.000Z", + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": "6.43" + }, + { + "borrowers": [ + "ANDERSON RHONDA K" + ], + "saleDate": "2005-01-14T00:00:00.000Z", + "recordingDate": "2005-01-20T00:00:00.000Z", + "dueDate": "2035-02-01T00:00:00.000Z", + "lenderName": "JPMORGAN CHASE BANK NA", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "AH", + "loanType": "Unknown (DEFAULT)", + "loanAmount": 156800, + "loanTermMonths": 360, + "interestRate": "5.67" + }, + { + "borrowers": [ + "ANDERSON RHONDA K" + ], + "saleDate": "2005-01-14T00:00:00.000Z", + "recordingDate": "2005-01-20T00:00:00.000Z", + "dueDate": "2035-01-28T00:00:00.000Z", + "lenderName": "JPMORGAN CHASE BANK NA", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 19000, + "loanTermMonths": 360, + "interestRate": "5.67" + }, + { + "borrowers": [ + "ANDERSON KYLE", + "ANDERSON RHONDA K" + ], + "saleDate": "2006-12-11T00:00:00.000Z", + "recordingDate": "2006-12-29T00:00:00.000Z", + "dueDate": "2026-12-19T00:00:00.000Z", + "lenderName": "USAA FSB", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 75299, + "loanTermMonths": 240, + "interestRate": "6.33" + }, + { + "borrowers": [ + "ANDRESON RHONDA K", + "ANDERSON KYLE" + ], + "saleDate": "2012-11-13T00:00:00.000Z", + "recordingDate": "2013-01-18T00:00:00.000Z", + "dueDate": "2027-12-01T00:00:00.000Z", + "lenderName": "USAA FSB", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "U", + "loanType": "New Conventional", + "loanAmount": 112000, + "loanTermMonths": 180, + "interestRate": "3.31" + } + ], + "openLien": { + "allLoanTypes": [], + "juniorLoanTypes": [], + "totalOpenLienCount": 1, + "mortgages": [ + { + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": 6.43, + "loanType": "New Conventional", + "subordinateLoanFlag": false, + "equityFlag": false, + "helocFlag": false, + "pudRider": false, + "condoRider": false, + "multiFamilyRider": false, + "assumable": false, + "privateLender": false, + "secondHomeRider": false, + "sellerCarryBack": false, + "balloonRider": false + } + ] + }, + "owner": { + "fullName": "Kyle Anderson; Rhonda K Anderson", + "mailingAddress": { + "houseNumber": "26823", + "street": "26823 N 31st Dr", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85083", + "zipPlus4": "5837", + "formattedStreet": "N 31st Dr", + "streetNoUnit": "26823 N 31st Dr", + "hash": "fae16e6adfaf0bb0f306ba89bf7b4c1e" + }, + "names": [ + { + "first": "Kyle", + "last": "Anderson", + "full": "Kyle Anderson" + }, + { + "first": "Rhonda", + "middle": "K", + "last": "Anderson", + "full": "Rhonda K Anderson" + } + ], + "ownerOccupied": true, + "mailingOptOut": false, + "hasLandline": true, + "ownerStatusTypeCode": "B", + "ownerStatusType": "Individual", + "ownershipRightsCode": "AT", + "ownershipRights": "Joint Tenants" + }, + "propertyOwnerProfile": { + "averageYearBuilt": 2001, + "propertiesCount": 1, + "propertiesTotalEquity": 449821, + "propertiesTotalEstimatedValue": 449821 + }, + "quickLists": { + "absenteeOwner": false, + "absenteeOwnerInState": false, + "absenteeOwnerOutOfState": false, + "activeListing": false, + "activeAuction": false, + "cashBuyer": false, + "corporateOwned": false, + "expiredListing": false, + "freeAndClear": true, + "highEquity": true, + "inherited": false, + "listedBelowMarketPrice": false, + "lowEquity": false, + "mailingAddressVacant": false, + "onMarket": false, + "outOfStateOwner": false, + "ownerOccupied": true, + "pendingListing": false, + "preforeclosure": false, + "recentlySold": false, + "samePropertyAndMailingAddress": true, + "taxDefault": false, + "tiredLandlord": false, + "unknownEquity": false, + "vacant": false, + "hasHoa": true, + "hasHoaFees": true, + "canceledListing": false, + "noticeOfSale": false, + "noticeOfDefault": false, + "noticeOfLisPendens": false + }, + "sale": { + "lastTransfer": { + "documentNumber": "2013-0056003", + "documentType": "Intra-Family Transfer", + "priceCodeDescription": "Transfer Tax on document indicated as EXEMPT.", + "priceCode": "Z", + "recordingDate": "2013-01-18T00:00:00.000Z", + "saleDate": "2012-11-13T00:00:00.000Z" + }, + "lastSale": { + "saleBuyers": [ + "Michael R Torres" + ], + "saleSellers": [ + "BlueSky Holdings LLC" + ], + "salePriceIsEstimated": false + }, + "flipLength": 0, + "flipLengthCategory": 0, + "flipProfit": 0, + "priorTransfer": { + "documentNumber": "2005-0080877", + "documentType": "Intra-Family Transfer", + "priceCodeDescription": "Non-arms length transaction.", + "priceCode": "N", + "salePriceIsEstimated": false + }, + "priorSale": { + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleBuyers": [ + "BlueSky Holdings LLC" + ], + "saleSellers": [ + "Estate Of Dorothy J Miller" + ], + "salePriceIsEstimated": false + } + }, + "tax": { + "taxAmount": 19, + "taxYear": 2023, + "taxDelinquentYear": 2022, + "taxRateCodeArea": "971300" + }, + "valuation": { + "estimatedValue": 449821, + "priceRangeMin": 382348, + "priceRangeMax": 517294, + "standardDeviation": 12, + "confidenceScore": 88, + "asOfDate": "2024-02-28T00:00:00.000Z", + "equityCurrentEstimatedBalance": 449821, + "ltv": 0, + "equityPercent": 100 + }, + "meta": { + "propertyDateModified": "2024-04-11T09:54:59.574Z" + } + } +} diff --git a/modules/watchSearchSessions/watchSearchSessions.communication.iml.json b/modules/watchSearchSessions/watchSearchSessions.communication.iml.json new file mode 100644 index 0000000..adfa25d --- /dev/null +++ b/modules/watchSearchSessions/watchSearchSessions.communication.iml.json @@ -0,0 +1,17 @@ +{ + "url": "/v2/property/search-sessions", + "method": "GET", + "qs": { + "take": 100 + }, + "response": { + "iterate": "{{body.results.sessions}}", + "output": "{{item}}", + "trigger": { + "type": "date", + "date": "{{item.createdAt}}", + "id": "{{item.id}}", + "order": "unordered" + } + } +} diff --git a/modules/watchSearchSessions/watchSearchSessions.interface.iml.json b/modules/watchSearchSessions/watchSearchSessions.interface.iml.json new file mode 100644 index 0000000..ced37e9 --- /dev/null +++ b/modules/watchSearchSessions/watchSearchSessions.interface.iml.json @@ -0,0 +1,22 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "totalDelivered", + "type": "number", + "label": "Total delivered" + }, + { + "name": "lastRequestAt", + "type": "text", + "label": "Last request at" + }, + { + "name": "createdAt", + "type": "text", + "label": "Created at" + } +] diff --git a/modules/watchSearchSessions/watchSearchSessions.samples.iml.json b/modules/watchSearchSessions/watchSearchSessions.samples.iml.json new file mode 100644 index 0000000..c4962d2 --- /dev/null +++ b/modules/watchSearchSessions/watchSearchSessions.samples.iml.json @@ -0,0 +1,6 @@ +{ + "id": "monthly-leads-az-85020", + "totalDelivered": 25, + "lastRequestAt": "2026-10-28T23:17:54.592Z", + "createdAt": "2026-10-01T16:02:11.120Z" +} diff --git a/modules/watchSearchSessions/watchSearchSessions.static-params.iml.json b/modules/watchSearchSessions/watchSearchSessions.static-params.iml.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/modules/watchSearchSessions/watchSearchSessions.static-params.iml.json @@ -0,0 +1 @@ +[] diff --git a/modules/watchSessionProperties/watchSessionProperties.communication.iml.json b/modules/watchSessionProperties/watchSessionProperties.communication.iml.json new file mode 100644 index 0000000..7274aa0 --- /dev/null +++ b/modules/watchSessionProperties/watchSessionProperties.communication.iml.json @@ -0,0 +1,33 @@ +{ + "url": "/v1/property/search", + "method": "POST", + "body": { + "searchCriteria": { + "{{...}}": "{{if(parameters.additionalCriteria, parseJSON(parameters.additionalCriteria), undefined)}}", + "query": "{{ifempty(parameters.query, undefined)}}", + "quickLists": "{{if(length(parameters.quickLists) > 0, parameters.quickLists, undefined)}}", + "orQuickLists": "{{if(length(parameters.orQuickLists) > 0, parameters.orQuickLists, undefined)}}" + }, + "options": { + "searchSession": { + "id": "{{parameters.sessionId}}" + }, + "take": "{{ifempty(parameters.take, 25)}}", + "useCursorPagination": true + } + }, + "response": { + "iterate": "{{body.results.properties}}", + "output": { + "{{...}}": "{{item}}", + "id": "{{item._id}}", + "searchMeta": "{{body.results.meta.results}}" + }, + "trigger": { + "type": "date", + "date": "{{now}}", + "id": "{{item._id}}", + "order": "unordered" + } + } +} diff --git a/modules/watchSessionProperties/watchSessionProperties.interface.iml.json b/modules/watchSessionProperties/watchSessionProperties.interface.iml.json new file mode 100644 index 0000000..690f15b --- /dev/null +++ b/modules/watchSessionProperties/watchSessionProperties.interface.iml.json @@ -0,0 +1,80 @@ +[ + { + "name": "id", + "type": "text", + "label": "Id" + }, + { + "name": "_id", + "type": "text", + "label": "Id" + }, + { + "name": "address", + "type": "collection", + "label": "Address", + "spec": [ + { + "name": "street", + "type": "text", + "label": "Street" + }, + { + "name": "city", + "type": "text", + "label": "City" + }, + { + "name": "state", + "type": "text", + "label": "State" + }, + { + "name": "zip", + "type": "text", + "label": "Zip" + }, + { + "name": "county", + "type": "text", + "label": "County" + } + ] + }, + { + "name": "general", + "type": "collection", + "label": "General", + "spec": [ + { + "name": "propertyTypeCategory", + "type": "text", + "label": "Property type category" + } + ] + }, + { + "name": "quickLists", + "type": "collection", + "label": "Quick lists", + "spec": [ + { + "name": "vacant", + "type": "boolean", + "label": "Vacant" + } + ] + }, + { + "name": "searchMeta", + "type": "collection", + "label": "Search meta", + "spec": [ + { + "name": "totalCount", + "type": "number", + "label": "Total count" + } + ] + } +] diff --git a/modules/watchSessionProperties/watchSessionProperties.samples.iml.json b/modules/watchSessionProperties/watchSessionProperties.samples.iml.json new file mode 100644 index 0000000..317d8fc --- /dev/null +++ b/modules/watchSessionProperties/watchSessionProperties.samples.iml.json @@ -0,0 +1,20 @@ +{ + "id": "09201d3bd3134f9380704014781fb4c0", + "_id": "09201d3bd3134f9380704014781fb4c0", + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa" + }, + "general": { + "propertyTypeCategory": "Single Family" + }, + "quickLists": { + "vacant": false + }, + "searchMeta": { + "totalCount": 1 + } +} diff --git a/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json b/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json new file mode 100644 index 0000000..d9cc1de --- /dev/null +++ b/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json @@ -0,0 +1,361 @@ +[ + { + "name": "sessionId", + "label": "Session ID", + "required": true, + "help": "Your own name for the session: letters, numbers, dash or underscore, up to 128 characters. Reusing the same ID continues the session, so you are never billed twice for the same property. A team can hold 100 sessions.", + "type": "text", + "validate": { + "pattern": "^[A-Za-z0-9_-]{1,128}$" + } + }, + { + "name": "query", + "label": "Location Query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\".", + "type": "text" + }, + { + "name": "quickLists", + "label": "Quick Lists (Include)", + "help": "Properties must match ALL selected lists.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "orQuickLists", + "label": "Quick Lists (Match Any)", + "help": "Properties must match AT LEAST ONE selected list.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "additionalCriteria", + "label": "Additional Criteria (JSON)", + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored. Optional JSON object with extra filters, for example {\"general\":{\"propertyTypeCategory\":{\"equals\":\"Single Family\"}}}. Fields you fill in above take priority.", + "type": "text" + }, + { + "name": "take", + "label": "Max Results", + "help": "Whole number from 1 to 100. Default 25. You are billed per property delivered on each check.", + "type": "integer", + "default": 25, + "validate": { + "min": 1, + "max": 100 + } + } +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..452b08f --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "batchdata-make", + "version": "0.2.0", + "private": true, + "description": "BatchData custom app for Make", + "type": "module", + "license": "MIT", + "scripts": { + "generate": "node scripts/generate.mjs", + "test": "node --test \"test/*.test.mjs\"" + }, + "engines": { "node": ">=20" } +} diff --git a/rpcs/listMonitors/listMonitors.communication.iml.json b/rpcs/listMonitors/listMonitors.communication.iml.json new file mode 100644 index 0000000..f75f025 --- /dev/null +++ b/rpcs/listMonitors/listMonitors.communication.iml.json @@ -0,0 +1,11 @@ +{ + "url": "/v2/property-subscription", + "method": "GET", + "response": { + "iterate": "{{body.results.subscriptions}}", + "output": { + "label": "{{item.id}}", + "value": "{{item.id}}" + } + } +} diff --git a/rpcs/listMonitors/listMonitors.params.iml.json b/rpcs/listMonitors/listMonitors.params.iml.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/rpcs/listMonitors/listMonitors.params.iml.json @@ -0,0 +1 @@ +[] diff --git a/rpcs/listSearchSessions/listSearchSessions.communication.iml.json b/rpcs/listSearchSessions/listSearchSessions.communication.iml.json new file mode 100644 index 0000000..0c3e69d --- /dev/null +++ b/rpcs/listSearchSessions/listSearchSessions.communication.iml.json @@ -0,0 +1,14 @@ +{ + "url": "/v2/property/search-sessions", + "method": "GET", + "qs": { + "take": 100 + }, + "response": { + "iterate": "{{body.results.sessions}}", + "output": { + "label": "{{item.id}}", + "value": "{{item.id}}" + } + } +} diff --git a/rpcs/listSearchSessions/listSearchSessions.params.iml.json b/rpcs/listSearchSessions/listSearchSessions.params.iml.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/rpcs/listSearchSessions/listSearchSessions.params.iml.json @@ -0,0 +1 @@ +[] diff --git a/scripts/catalog-modules.mjs b/scripts/catalog-modules.mjs new file mode 100644 index 0000000..fbb3c66 --- /dev/null +++ b/scripts/catalog-modules.mjs @@ -0,0 +1,414 @@ +// Turns the shared catalog (scripts/catalog.json) into Make modules, webhooks and RPCs. +// The catalog is the single source of truth shared with the Zapier and n8n integrations. +import fs from 'node:fs'; +import { root } from './lib.mjs'; + +export const catalog = JSON.parse(fs.readFileSync(`${root}/scripts/catalog.json`, 'utf8')); +const fx = (n) => JSON.parse(fs.readFileSync(`${root}/test/fixtures/${n}.json`, 'utf8')); + +const camel = (s) => s.replace(/[-_]+([a-z0-9])/g, (_, c) => c.toUpperCase()); +const moduleKey = (key) => camel(key); +const opt = (c) => ({ label: c.label ?? c.value ?? c, value: c.value ?? c }); + +// ---------- parameters (what the user fills in) ---------- +const nestSpec = (rowFields) => { + const tree = {}; + for (const f of rowFields) { + const segs = f.path.split('.'); + let node = tree; + segs.forEach((s, i) => { + if (i === segs.length - 1) node[s] = f; + else node = node[s] ??= {}; + }); + } + const build = (n) => + Object.entries(n).map(([name, v]) => + v.key !== undefined && v.path !== undefined + ? { name, type: 'text', label: v.label, ...(v.help ? { help: v.help } : {}) } + : { name, type: 'collection', label: name === 'propertyAddress' ? 'Property address' : name[0].toUpperCase() + name.slice(1), spec: build(v) }, + ); + return build(tree); +}; + +const lastSeg = (p) => p.split('.').pop(); + +const paramFor = (f, name = camel(f.key), dynamicRpc) => { + const base = { name, label: f.label }; + if (f.required) base.required = true; + if (f.help) base.help = f.help; + switch (f.type) { + case 'rows': + return { ...base, type: 'array', spec: nestSpec(f.rowFields) }; + case 'list': + return { ...base, type: 'array', spec: f.choices ? { type: 'select', options: f.choices.map(opt) } : { type: 'text' } }; + case 'choice': + return { ...base, type: 'select', options: f.choices.map(opt), ...(f.default !== undefined ? { default: f.default } : {}) }; + case 'boolean': + return { ...base, type: 'boolean', ...(f.default !== undefined ? { default: f.default } : {}) }; + case 'integer': + return { ...base, type: 'integer', ...(f.default !== undefined ? { default: f.default } : {}), ...(f.min !== undefined || f.max !== undefined ? { validate: { ...(f.min !== undefined ? { min: f.min } : {}), ...(f.max !== undefined ? { max: f.max } : {}) } } : {}) }; + case 'number': + return { ...base, type: 'number' }; + case 'date': + return { ...base, type: 'date' }; + case 'password': + return { ...base, type: 'password' }; + case 'json': + return { ...base, type: 'text', help: `${f.help ? `${f.help} ` : ''}Optional JSON object with extra filters, for example {"general":{"propertyTypeCategory":{"equals":"Single Family"}}}. Fields you fill in above take priority.` }; + default: { + if (dynamicRpc && f.in === 'path') return { ...base, type: 'select', options: `rpc://${dynamicRpc}` }; + return { ...base, type: 'text', ...(f.pattern ? { validate: { pattern: f.pattern } } : f.in === 'path' ? { validate: { pattern: '^[A-Za-z0-9_-]+$' } } : {}) }; + } + } +}; + +const RPC_FOR = { new_search_session: 'listSearchSessions', new_monitor: 'listMonitors' }; + +// Optional nested filters (for example request.status.equals) are grouped into one collection +// parameter that mirrors the wire shape, so an untouched group is dropped instead of sent as {}. +const rootLen = (segs) => (/^\d+$/.test(segs[1] ?? '') ? 2 : 1); +const plain = (f) => !f.local && !f.when && !f.merge && f.in === undefined && f.path && f.type !== 'rows'; +export const groupsOf = (op) => { + const found = {}; + for (const f of op.fields.filter(plain)) { + const segs = f.path.split('.'); + const rest = segs.slice(rootLen(segs)); + if (rest.length < 2) continue; + (found[`${segs.slice(0, rootLen(segs)).join('.')}|${rest[0]}`] ??= []).push(f); + } + const out = {}; + for (const [k, fs_] of Object.entries(found)) { + if (fs_.some((f) => f.required)) continue; + const [root, name] = k.split('|'); + out[k] = { root, name, fields: fs_ }; + } + return out; +}; +const groupedKeys = (op) => new Set(Object.values(groupsOf(op)).flatMap((g) => g.fields.map((f) => f.key))); +const title = (n) => n.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (c) => c.toUpperCase()); +const nestFields = (fields, rootN) => { + const tree = {}; + for (const f of fields) { + const rest = f.path.split('.').slice(rootN); + let node = tree; + rest.forEach((s, i) => { + if (i === rest.length - 1) node[s] = f; + else node = node[s] ??= {}; + }); + } + const build = (n) => + Object.entries(n).map(([name, v]) => + v.key !== undefined && v.path !== undefined + ? (() => { + const p = paramFor(v.type === 'date' ? { ...v, type: 'string', help: `${v.help ? `${v.help} ` : ''}Format YYYY-MM-DD.` } : v, name); + delete p.required; + return p; + })() + : { name, type: 'collection', label: title(name), spec: build(v) }, + ); + return build(tree); +}; + +const paramsFor = (op) => { + const out = []; + const groups = {}; + const grouped = groupedKeys(op); + const doneGroups = new Set(); + for (const f of op.fields) { + if (f.local) continue; + if (grouped.has(f.key)) { + const segs = f.path.split('.'); + const gk = `${segs.slice(0, rootLen(segs)).join('.')}|${segs[rootLen(segs)]}`; + if (!doneGroups.has(gk)) { + doneGroups.add(gk); + const g = groupsOf(op)[gk]; + out.push({ name: g.name, type: 'collection', label: title(g.name), spec: nestFields(g.fields, rootLen(segs) + 1) }); + } + continue; + } + if (f.when) { + const g = camel(f.when.equals); + (groups[g] ??= { equals: f.when.equals, fields: [] }).fields.push(f); + continue; + } + out.push(paramFor(f, camel(f.key), RPC_FOR[op.dynamic])); + } + for (const [name, g] of Object.entries(groups)) { + out.push({ + name, + type: 'collection', + label: g.equals === 'kinesis' ? 'AWS Kinesis settings' : 'Azure Event Hub settings', + help: `Used when Delivery type is "${g.equals}".`, + spec: g.fields.map((f) => paramFor(f, lastSeg(f.path))), + }); + } + return out; +}; + +// ---------- request bodies (what is sent) ---------- +const P = (k) => `parameters.${k}`; +const valueExpr = (f, name = camel(f.key)) => { + const p = P(name); + switch (f.type) { + case 'rows': + return `{{${p}}}`; + case 'list': + return `{{if(length(${p}) > 0, ${p}, undefined)}}`; + case 'boolean': + return f.emit === 'always' ? `{{if(${p} == true, true, false)}}` : `{{if(${p} == true, true, undefined)}}`; + case 'date': + return `{{if(${p}, formatDate(${p}, 'YYYY-MM-DD'), undefined)}}`; + case 'json': + return `{{if(${p}, parseJSON(${p}), undefined)}}`; + case 'integer': + case 'number': + return `{{ifempty(${p}, ${f.default !== undefined ? JSON.stringify(f.default) : 'undefined'})}}`; + default: + if (f.required) return `{{${p}}}`; + return f.default !== undefined ? `{{ifempty(${p}, ${JSON.stringify(f.default)})}}` : `{{ifempty(${p}, undefined)}}`; + } +}; + +const arrayify = (n) => { + if (Array.isArray(n) || n === null || typeof n !== 'object') return n; + const keys = Object.keys(n); + const mapped = Object.fromEntries(keys.map((k) => [k, arrayify(n[k])])); + return keys.length && keys.every((k) => /^\d+$/.test(k)) ? keys.map((k) => mapped[k]) : mapped; +}; + +export const buildBody = (op, { only } = {}) => { + const tree = {}; + const at = (segs, create = true) => { + let n = tree; + for (const s of segs) n = n[s] ??= {}; + return n; + }; + const grouped = groupedKeys(op); + const fields = op.fields.filter((f) => !f.local && f.in !== 'path' && f.in !== 'query' && (!only || only(f)) && !grouped.has(f.key)); + for (const g of Object.values(groupsOf(op))) { + if (only && !g.fields.every(only)) continue; + at(g.root.split('.'))[g.name] = `{{ifempty(parameters.${g.name}, undefined)}}`; + } + for (const f of fields.filter((x) => x.merge)) at(f.path.split('.'))['{{...}}'] = valueExpr(f); + for (const f of fields.filter((x) => !x.merge && !x.when)) { + const segs = f.path.split('.'); + at(segs.slice(0, -1))[segs.at(-1)] = valueExpr(f); + } + const groups = {}; + for (const f of fields.filter((x) => x.when)) (groups[f.when.equals] ??= []).push(f); + for (const [eq, fs_] of Object.entries(groups)) { + const target = fs_[0].path.split('.').slice(0, -1); + const expr = `{{if(parameters.${camel(fs_[0].when.field)} == '${eq}', parameters.${camel(eq)}, undefined)}}`; + if (target.length === 1) at(target)['{{...}}'] = expr; + else at(target.slice(0, -1))[target.at(-1)] = expr; + } + for (const fx_ of op.fixed ?? []) { + const segs = fx_.path.split('.'); + at(segs.slice(0, -1))[segs.at(-1)] = fx_.value; + } + return arrayify(tree); +}; + +const api = (p) => p.replace(/^\/api/, ''); +const urlFor = (op) => api(op.http.path).replace('{id}', "{{replace(replace(parameters.id, '/', '_'), '.', '_')}}"); + +const responseFor = (op) => { + const r = op.result; + const get = (p) => `{{body.${p}}}`; + if (r.list) { + const iterate = r.filter + ? { container: get(r.list), condition: `{{item.${r.filter.path} == ${JSON.stringify(r.filter.equals)}}}` } + : get(r.list); + const attach = Object.entries(r.attach ?? {}).map(([k, p]) => [k, get(p)]); + return { iterate, output: attach.length ? { '{{...}}': '{{item}}', ...Object.fromEntries(attach) } : '{{item}}' }; + } + if (r.item) return { output: get(r.item) }; + if (r.compose) return { output: Object.fromEntries(Object.entries(r.compose).map(([k, p]) => [k, get(p)])) }; + if (r.static) { + return { output: { ...r.static, ...Object.fromEntries(Object.entries(r.echo ?? {}).map(([k, f]) => [k, `{{parameters.${camel(f)}}}`])) } }; + } + throw new Error(`no result mapping for ${op.key}`); +}; + +const requestFor = (op) => { + const queryFields = op.fields.filter((f) => f.in === 'query'); + const body = op.http.method === 'GET' || op.http.method === 'DELETE' ? undefined : buildBody(op); + return { + url: urlFor(op), + method: op.http.method, + ...(queryFields.length ? { qs: Object.fromEntries(queryFields.map((f) => [f.qs, valueExpr(f)])) } : {}), + ...(body ? { body } : {}), + response: responseFor(op), + }; +}; + +const CRUD = (op) => (op.key.startsWith('create_') ? 'create' : op.key.startsWith('delete_') ? 'delete' : 'read'); + +const describe = (op) => + op.requireOneOf ? `${op.description} Fill in at least one of: ${op.requireOneOf.map((k) => op.fields.find((f) => f.key === k).label).join(', ')}.` : op.description; + +// Result fields the API sometimes omits from the catalog sample; shown in the output list anyway. +const SAMPLE_FILL = { warnings: [], nextPageCursor: 'eyJwYWdlIjoyfQ' }; + +// ---------- modules ---------- +export const catalogModules = {}; +for (const op of catalog.ops) { + const key = moduleKey(op.key); + catalogModules[key] = { + moduleType: op.kind === 'search' ? 'search' : 'action', + actionCrud: CRUD(op), + catalogKey: op.key, + label: op.label, + description: describe(op), + communication: requestFor(op), + params: paramsFor(op), + sampleFrom: () => ({ ...op.sample, ...Object.fromEntries(Object.keys(op.result.attach ?? {}).filter((k) => !(k in op.sample)).map((k) => [k, SAMPLE_FILL[k] ?? {}])) }), + }; +} + +const staticFromFields = (op) => paramsFor(op); + +const trig = (key) => catalog.triggers.find((t) => t.key === key); + +{ + const t = trig('new_search_session'); + catalogModules.watchSearchSessions = { + moduleType: 'trigger', + catalogKey: t.key, + label: 'Watch New Search Sessions', + description: t.description, + communication: { + url: api(t.http.path), + method: 'GET', + qs: { take: t.fixedQuery.take }, + response: { + iterate: `{{body.${t.result.list}}}`, + output: '{{item}}', + trigger: { type: 'date', date: '{{item.createdAt}}', id: '{{item.id}}', order: 'unordered' }, + }, + }, + staticParams: [], + sampleFrom: () => t.sample, + }; +} +{ + // Search-session properties carry neither a date nor an ordered ID. The session itself never + // delivers the same property twice, so the poll time is used as the trigger date. + const t = trig('new_session_property'); + catalogModules.watchSessionProperties = { + moduleType: 'trigger', + catalogKey: t.key, + label: t.label.replace('New Property in Search Session', 'Watch New Properties in a Search Session'), + description: `${t.description} Every scheduled run of the scenario, and every "Run once", is a billed search.`, + communication: { + url: api(t.http.path), + method: 'POST', + body: buildBody(t), + response: { + iterate: `{{body.${t.result.list}}}`, + output: { '{{...}}': '{{item}}', id: '{{item._id}}', searchMeta: `{{body.${t.result.attach.searchMeta}}}` }, + trigger: { type: 'date', date: '{{now}}', id: '{{item._id}}', order: 'unordered' }, + }, + }, + staticParams: staticFromFields(t), + sampleFrom: () => ({ ...t.sample, searchMeta: { totalCount: 1 } }), + }; +} + +// ---------- instant trigger: webhook component + two modules ---------- +const ev = trig('monitor_event'); +const criteriaOnly = (f) => !f.local; +const webhookParams = ev.fields.filter(criteriaOnly).map((f) => paramFor(f)); +export const webhooks = { + monitorEvents: { + label: 'Property monitor events', + webhookType: 'web', + communication: { + condition: "{{length(ifempty(body.eventId, '')) > 0}}", + uid: '{{body.eventId}}', + output: { '{{...}}': '{{body}}', id: '{{body.eventId}}' }, + }, + params: webhookParams, + attach: { + url: api(ev.http.path), + method: 'POST', + body: { + ...buildBody(ev, { only: criteriaOnly }), + deliveryConfig: { type: 'webhook', url: '{{webhook.url}}' }, + }, + response: { data: { externalHookId: '{{body.result.subscriptionId}}' } }, + }, + detach: { + url: api(ev.http.deletePath).replace('{id}', '{{webhook.externalHookId}}'), + method: 'DELETE', + }, + }, +}; +export const EVENT_LIMITS_NOTE = + 'Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event), so the same event can arrive more than once. Use the Event ID to skip repeats.'; + +const evBase = ev.description + .replace('Turning the Zap or scenario on', 'Turning the scenario on') + .replace('turning it off', 'turning it off') + .replace(' turn on "Include Property Record" to also look up the full property record.', ' use "Watch Property Monitor Events with Property Record" to also look up the full property record.') + .replace(/ The optional property lookup is billed per event, per matched property\./, ''); +catalogModules.watchMonitorEvents = { + moduleType: 'instant_trigger', + catalogKey: ev.key, + webhook: 'monitorEvents', + label: 'Watch Property Monitor Events (Instant)', + description: evBase, + communication: {}, + staticParams: [], + sampleFrom: () => ev.sample, +}; +catalogModules.watchMonitorEventsWithProperty = { + moduleType: 'instant_trigger', + catalogKey: ev.key, + webhook: 'monitorEvents', + label: 'Watch Property Monitor Events with Property Record (Instant, Billed)', + description: `${evBase} This version also looks up the full property record for every event. The lookup is billed per event, per matched property, on top of the monitor charges.`, + communication: { + url: api(ev.hydrate.path), + method: 'POST', + body: { requests: [{ propertyId: '{{payload.propertyId}}' }] }, + response: { + output: { '{{...}}': '{{payload}}', [ev.hydrate.into]: `{{first(body.${ev.hydrate.listPath})}}` }, + }, + }, + staticParams: [], + sampleFrom: () => ({ ...ev.sample, property: fx('property-lookup-all-attributes').results.properties[0] }), +}; + +// ---------- RPCs for dropdowns ---------- +export const rpcs = { + listSearchSessions: { + label: 'List search sessions', + communication: { + url: '/v2/property/search-sessions', + method: 'GET', + qs: { take: 100 }, + response: { iterate: '{{body.results.sessions}}', output: { label: '{{item.id}}', value: '{{item.id}}' } }, + }, + params: [], + }, + listMonitors: { + label: 'List property monitors', + communication: { + url: '/v2/property-subscription', + method: 'GET', + response: { iterate: '{{body.results.subscriptions}}', output: { label: '{{item.id}}', value: '{{item.id}}' } }, + }, + params: [], + }, +}; + +export const catalogGroups = [ + { label: 'Properties', modules: ['batchLookupProperty', 'getPermits'] }, + { label: 'Owner contact', modules: ['reverseSkipTrace', 'batchSkipTrace'] }, + { label: 'Addresses', modules: ['autocompleteAddress', 'geocodeAddress', 'reverseGeocode', 'batchVerifyAddress'] }, + { label: 'Investors', modules: ['investorBuybox', 'investorProperties'] }, + { label: 'Search sessions', modules: ['createSearchSession', 'listSearchSessions', 'deleteSearchSession'] }, + { label: 'Property monitors', modules: ['createMonitor', 'listMonitors', 'getMonitor', 'deleteMonitor'] }, + { label: 'Triggers', modules: ['watchSearchSessions', 'watchSessionProperties', 'watchMonitorEvents', 'watchMonitorEventsWithProperty'] }, +]; diff --git a/scripts/catalog.json b/scripts/catalog.json new file mode 100644 index 0000000..c1b240a --- /dev/null +++ b/scripts/catalog.json @@ -0,0 +1,3084 @@ +{ + "meta": { + "version": "0.2.0", + "supportEmail": "support@batchservice.com", + "quickLists": [ + "absentee-owner", + "active-auction", + "active-listing", + "canceled-listing", + "cash-buyer", + "corporate-owned", + "expired-listing", + "failed-listing", + "fix-and-flip", + "free-and-clear", + "for-sale-by-owner", + "has-hoa", + "has-hoa-fees", + "high-equity", + "inherited", + "involuntary-lien", + "in-state-absentee-owner", + "listed-below-market-price", + "low-equity", + "mailing-address-vacant", + "notice-of-default", + "notice-of-lis-pendens", + "notice-of-sale", + "on-market", + "out-of-state-absentee-owner", + "out-of-state-owner", + "owner-occupied", + "pending-listing", + "preforeclosure", + "recently-sold", + "same-property-and-mailing-address", + "senior-owner", + "tax-default", + "tired-landlord", + "trust-owned", + "unknown-equity", + "vacant", + "vacant-lot" + ] + }, + "ops": [ + { + "key": "reverse_skip_trace", + "resource": "property", + "kind": "search", + "action": true, + "noun": "Reverse Skip Trace Result", + "label": "Reverse Skip Trace (Phone or Email)", + "description": "Finds the person (and associated property addresses) behind a phone number or an email address using the V3 reverse skip trace. Returns up to 3 persons. Billed per matched person; requests with no match are not billed. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "skipTraceLike": true, + "http": { + "method": "POST", + "path": "/api/v3/property/reverse-skip-trace" + }, + "fields": [ + { + "key": "phone", + "label": "Phone Number", + "type": "string", + "path": "requests.0.phone", + "help": "A US phone number. Fill in a phone number or an email address." + }, + { + "key": "email", + "label": "Email Address", + "type": "string", + "path": "requests.0.email" + }, + { + "key": "request_id", + "label": "Your Reference ID", + "type": "string", + "path": "requests.0.requestId", + "help": "Optional. Echoed back in the result." + }, + { + "key": "include_tcpa_flagged", + "label": "Include TCPA-Flagged Phones", + "type": "boolean", + "path": "options.includeTCPABlacklistedPhones", + "emit": "always", + "default": false, + "help": "Off by default: phone numbers flagged as TCPA litigators are left out. Turn on only with a documented compliance basis. Flagged numbers carry \"tcpa\": true in the result." + } + ], + "requireOneOf": [ + "phone", + "email" + ], + "result": { + "list": "result.data", + "filter": { + "path": "meta.matched", + "equals": true + } + }, + "sample": { + "input": { + "phone": "6025550142", + "requestId": "ref-1" + }, + "persons": [ + { + "name": { + "first": "Dana", + "last": "Reyes", + "full": "Dana Reyes" + }, + "phoneNumbers": [ + { + "number": "6025550142", + "type": "Mobile", + "tcpa": false, + "dnc": false + } + ], + "emails": [ + "dana@example.com" + ] + } + ], + "meta": { + "matched": true, + "error": false + } + } + }, + { + "key": "get_permits", + "resource": "property", + "kind": "search", + "noun": "Permit", + "label": "Get Property Permits", + "description": "Returns building permits for one property, one result per permit. Identify the property by address, BatchData property ID, or APN with county FIPS code. Billed per property requested, however many permits come back.", + "http": { + "method": "POST", + "path": "/api/v2/property/get-property-permits" + }, + "fields": [ + { + "key": "street", + "label": "Street", + "type": "string", + "path": "request.address.street", + "help": "Street address with house number, for example 2800 N 24th St." + }, + { + "key": "city", + "label": "City", + "type": "string", + "path": "request.address.city" + }, + { + "key": "state", + "label": "State", + "type": "string", + "path": "request.address.state", + "help": "Two-letter state code, for example AZ." + }, + { + "key": "zip", + "label": "ZIP Code", + "type": "string", + "path": "request.address.zip" + }, + { + "key": "property_id", + "label": "BatchData Property ID", + "type": "string", + "path": "request.propertyId" + }, + { + "key": "apn", + "label": "APN", + "type": "string", + "path": "request.apn" + }, + { + "key": "county_fips_code", + "label": "County FIPS Code", + "type": "string", + "path": "request.countyFipsCode", + "help": "Use with APN." + }, + { + "key": "permit_status", + "label": "Permit Status", + "type": "choice", + "path": "request.status.equals", + "choices": [ + { + "value": "active", + "label": "active" + }, + { + "value": "final", + "label": "final" + }, + { + "value": "in_review", + "label": "in_review" + }, + { + "value": "inactive", + "label": "inactive" + } + ] + }, + { + "key": "permit_type", + "label": "Permit Type Contains", + "type": "string", + "path": "request.type.contains" + }, + { + "key": "description_contains", + "label": "Description Contains", + "type": "string", + "path": "request.description.contains" + }, + { + "key": "tags", + "label": "Permit Tags (Match Any)", + "type": "list", + "path": "request.allTags.inList", + "choices": [ + { + "value": "addition", + "label": "addition" + }, + { + "value": "adu", + "label": "adu" + }, + { + "value": "bathroom", + "label": "bathroom" + }, + { + "value": "battery", + "label": "battery" + }, + { + "value": "demolition", + "label": "demolition" + }, + { + "value": "electrical", + "label": "electrical" + }, + { + "value": "electricMeter", + "label": "electricMeter" + }, + { + "value": "evCharger", + "label": "evCharger" + }, + { + "value": "fireSprinkler", + "label": "fireSprinkler" + }, + { + "value": "gas", + "label": "gas" + }, + { + "value": "generator", + "label": "generator" + }, + { + "value": "grading", + "label": "grading" + }, + { + "value": "heatPump", + "label": "heatPump" + }, + { + "value": "hvac", + "label": "hvac" + }, + { + "value": "inspectionPassed", + "label": "inspectionPassed" + }, + { + "value": "kitchen", + "label": "kitchen" + }, + { + "value": "newConstruction", + "label": "newConstruction" + }, + { + "value": "plumbing", + "label": "plumbing" + }, + { + "value": "poolAndHotTub", + "label": "poolAndHotTub" + }, + { + "value": "remodel", + "label": "remodel" + }, + { + "value": "roofing", + "label": "roofing" + }, + { + "value": "solar", + "label": "solar" + }, + { + "value": "waterHeater", + "label": "waterHeater" + }, + { + "value": "windowDoor", + "label": "windowDoor" + } + ] + }, + { + "key": "issued_from", + "label": "Issued From (YYYY-MM-DD)", + "type": "date", + "path": "request.issueDate.minDate" + }, + { + "key": "issued_to", + "label": "Issued To (YYYY-MM-DD)", + "type": "date", + "path": "request.issueDate.maxDate" + }, + { + "key": "job_value_min", + "label": "Job Value (Min)", + "type": "number", + "path": "request.jobValue.min" + }, + { + "key": "job_value_max", + "label": "Job Value (Max)", + "type": "number", + "path": "request.jobValue.max" + }, + { + "key": "take", + "label": "Max Results", + "type": "integer", + "path": "options.take", + "default": 25, + "min": 1, + "max": 100, + "help": "Whole number from 1 to 100. Default 25." + } + ], + "requireOneOf": [ + "street", + "property_id", + "apn" + ], + "result": { + "list": "result.permits", + "attach": { + "property": "result.property", + "searchMeta": "result.meta.results" + } + }, + "sample": { + "permitNumber": "BP-2026-004211", + "type": "Building", + "subtype": "Residential", + "status": "final", + "description": "Re-roof, composition shingle", + "issueDate": "2026-02-03", + "finalDate": "2026-02-20", + "jobValue": 14500, + "jurisdiction": "Phoenix", + "allTags": [ + "roofing" + ], + "property": { + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008" + } + }, + "searchMeta": { + "resultCount": 1, + "resultsFound": 1 + } + } + }, + { + "key": "batch_skip_trace", + "resource": "property", + "kind": "action", + "noun": "Batch Skip Trace", + "label": "Batch Skip Trace Properties (V3)", + "description": "Skip traces up to 100 properties in one call with the V3 skip trace. Returns one result per submitted property, each with a list of persons. Billed per property matched. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "skipTraceLike": true, + "http": { + "method": "POST", + "path": "/api/v3/property/skip-trace" + }, + "fields": [ + { + "key": "properties", + "label": "Properties", + "type": "rows", + "required": true, + "path": "requests", + "max": 100, + "help": "One row per property, up to 100. Billed per matched property.", + "rowFields": [ + { + "key": "street", + "label": "Street", + "type": "string", + "path": "propertyAddress.street" + }, + { + "key": "city", + "label": "City", + "type": "string", + "path": "propertyAddress.city" + }, + { + "key": "state", + "label": "State", + "type": "string", + "path": "propertyAddress.state" + }, + { + "key": "zip", + "label": "ZIP Code", + "type": "string", + "path": "propertyAddress.zip" + }, + { + "key": "owner_first_name", + "label": "Owner First Name", + "type": "string", + "path": "name.first" + }, + { + "key": "owner_last_name", + "label": "Owner Last Name", + "type": "string", + "path": "name.last" + }, + { + "key": "request_id", + "label": "Your Reference ID", + "type": "string", + "path": "requestId" + } + ] + }, + { + "key": "include_tcpa_flagged", + "label": "Include TCPA-Flagged Phones", + "type": "boolean", + "path": "options.includeTCPABlacklistedPhones", + "emit": "always", + "default": false, + "help": "Off by default: phone numbers flagged as TCPA litigators are left out. Turn on only with a documented compliance basis. Flagged numbers carry \"tcpa\": true in the result." + } + ], + "result": { + "compose": { + "results": "result.data", + "matchCount": "result.meta.results.matchCount", + "requestCount": "result.meta.results.requestCount" + } + }, + "sample": { + "results": [ + { + "input": { + "requestId": "row-1" + }, + "persons": [ + { + "name": { + "first": "Dana", + "last": "Reyes" + }, + "phoneNumbers": [ + { + "number": "6025550142", + "type": "Mobile", + "tcpa": false + } + ] + } + ], + "meta": { + "matched": true + } + } + ], + "matchCount": 1, + "requestCount": 1 + } + }, + { + "key": "batch_lookup_property", + "resource": "property", + "kind": "action", + "noun": "Batch Property Lookup", + "label": "Batch Look Up Properties", + "description": "Returns full property records for up to 100 addresses in one call. Billed per property matched.", + "http": { + "method": "POST", + "path": "/api/v1/property/lookup/all-attributes" + }, + "fields": [ + { + "key": "addresses", + "label": "Addresses", + "type": "rows", + "required": true, + "path": "requests", + "max": 100, + "help": "One row per property address, up to 100.", + "rowFields": [ + { + "key": "street", + "label": "Street", + "type": "string", + "path": "address.street" + }, + { + "key": "city", + "label": "City", + "type": "string", + "path": "address.city" + }, + { + "key": "state", + "label": "State", + "type": "string", + "path": "address.state" + }, + { + "key": "zip", + "label": "ZIP Code", + "type": "string", + "path": "address.zip" + }, + { + "key": "request_id", + "label": "Your Reference ID", + "type": "string", + "path": "requestId" + } + ] + }, + { + "key": "datasets", + "label": "Datasets", + "type": "list", + "path": "options.datasets", + "choices": [ + { + "value": "basic", + "label": "basic" + }, + { + "value": "comps", + "label": "comps" + }, + { + "value": "batchrank", + "label": "batchrank" + }, + { + "value": "contact", + "label": "contact" + }, + { + "value": "core", + "label": "core" + }, + { + "value": "deed", + "label": "deed" + }, + { + "value": "demographic", + "label": "demographic" + }, + { + "value": "foreclosure", + "label": "foreclosure" + }, + { + "value": "listing", + "label": "listing" + }, + { + "value": "mortgage-liens", + "label": "mortgage-liens" + }, + { + "value": "owner", + "label": "owner" + }, + { + "value": "permit", + "label": "permit" + }, + { + "value": "quicklist", + "label": "quicklist" + }, + { + "value": "valuation", + "label": "valuation" + } + ], + "help": "Optional. Limits the response to these datasets. \"basic\" and \"core\" cannot be combined." + } + ], + "result": { + "compose": { + "properties": "results.properties", + "matchCount": "results.meta.results.matchCount", + "noMatchCount": "results.meta.results.noMatchCount" + } + }, + "sample": { + "properties": [ + { + "_id": "09201d3bd3134f9380704014781fb4c0", + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa" + }, + "general": { + "propertyTypeCategory": "Single Family" + }, + "quickLists": { + "vacant": false + } + } + ], + "matchCount": 1, + "noMatchCount": 0 + } + }, + { + "key": "autocomplete_address", + "resource": "address", + "kind": "search", + "noun": "Address Suggestion", + "label": "Autocomplete Address", + "description": "Suggests addresses for partial text, one result per suggestion. Billed per call.", + "http": { + "method": "POST", + "path": "/api/v1/address/autocomplete" + }, + "fields": [ + { + "key": "query", + "label": "Partial Address", + "type": "string", + "required": true, + "path": "searchCriteria.query", + "help": "For example \"2800 N 24th\"." + }, + { + "key": "take", + "label": "Max Results", + "type": "integer", + "path": "options.take", + "default": 5, + "min": 1, + "max": 20, + "help": "Whole number from 1 to 20. Default 5." + }, + { + "key": "usps_verified_only", + "label": "USPS-Verified Addresses Only", + "type": "boolean", + "path": "options.uspsVerifiedAddresses" + } + ], + "result": { + "list": "result.suggestions" + }, + "sample": { + "propertyId": "09201d3bd3134f9380704014781fb4c0", + "name": "2800 N 24th St, Phoenix, AZ 85008", + "type": "address", + "address": "2800 n 24th st", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "latitude": 33.478332, + "longitude": -112.030474, + "propertyCount": 1, + "uspsVerified": true + } + }, + { + "key": "geocode_address", + "resource": "address", + "kind": "search", + "noun": "Geocoded Address", + "label": "Geocode Address", + "description": "Returns latitude, longitude and the standardized form of one address. Billed per address submitted.", + "http": { + "method": "POST", + "path": "/api/v1/address/geocode" + }, + "fields": [ + { + "key": "address", + "label": "Address", + "type": "string", + "required": true, + "path": "requests.0.address", + "help": "One full address, for example 2800 N 24th St, Phoenix, AZ 85008." + }, + { + "key": "request_id", + "label": "Your Reference ID", + "type": "string", + "path": "requests.0.requestId" + } + ], + "result": { + "list": "result.addresses" + }, + "sample": { + "street": "2800 N 24th St", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85008", + "fullAddress": "2800 N 24th St, Phoenix, Maricopa, AZ, 85008", + "latitude": 33.478332, + "longitude": -112.030474, + "geoStatus": "Rooftop", + "meta": { + "error": false, + "geocoded": true + } + } + }, + { + "key": "reverse_geocode", + "resource": "address", + "kind": "search", + "noun": "Nearby Address", + "label": "Reverse Geocode (Find Nearby Addresses)", + "description": "Returns the nearest addresses to a latitude and longitude, one result per address. Billed per geo-location submitted.", + "http": { + "method": "POST", + "path": "/api/v1/address/reverse-geocode" + }, + "fields": [ + { + "key": "latitude", + "label": "Latitude", + "type": "number", + "required": true, + "path": "request.latitude" + }, + { + "key": "longitude", + "label": "Longitude", + "type": "number", + "required": true, + "path": "request.longitude" + }, + { + "key": "property_only", + "label": "Property Addresses Only", + "type": "boolean", + "path": "options.showPropertyOnly" + } + ], + "result": { + "list": "result.addresses" + }, + "sample": { + "street": "2806 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa", + "latitude": 33.4787348, + "longitude": -112.0306996, + "types": [ + "Street" + ], + "geoStatus": "Rooftop", + "meta": { + "error": false + } + } + }, + { + "key": "batch_verify_address", + "resource": "address", + "kind": "action", + "noun": "Batch Address Verification", + "label": "Batch Verify Addresses", + "description": "Standardizes and validates up to 100 US addresses in one call. Billed per address submitted.", + "http": { + "method": "POST", + "path": "/api/v1/address/verify" + }, + "fields": [ + { + "key": "addresses", + "label": "Addresses", + "type": "rows", + "required": true, + "path": "requests", + "max": 100, + "help": "One row per address, up to 100.", + "rowFields": [ + { + "key": "street", + "label": "Street", + "type": "string", + "path": "street" + }, + { + "key": "city", + "label": "City", + "type": "string", + "path": "city" + }, + { + "key": "state", + "label": "State", + "type": "string", + "path": "state" + }, + { + "key": "zip", + "label": "ZIP Code", + "type": "string", + "path": "zip" + } + ] + } + ], + "result": { + "compose": { + "addresses": "results.addresses", + "requestCount": "results.meta.results.requestCount", + "normalizedCount": "results.meta.results.normalizedCount" + } + }, + "sample": { + "addresses": [ + { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa", + "meta": { + "verified": true + } + } + ], + "requestCount": 1, + "normalizedCount": 1 + } + }, + { + "key": "investor_buybox", + "resource": "investor", + "kind": "search", + "noun": "Investor", + "label": "Find Investors (Buy Box)", + "description": "Finds likely real estate investors for a subject property from nearby comparable sales, one result per investor with holdings, acquisitions and contacts. Billed according to your BatchData plan; contact support@batchservice.com for pricing. Results are data only. You are responsible for TCPA and Do Not Call compliance.", + "http": { + "method": "POST", + "path": "/api/v2/investor/buybox" + }, + "fields": [ + { + "key": "street", + "label": "Subject Property Street", + "type": "string", + "path": "searchCriteria.compAddress.street", + "help": "Street address with house number, for example 2800 N 24th St.", + "required": true + }, + { + "key": "city", + "label": "Subject Property City", + "type": "string", + "path": "searchCriteria.compAddress.city", + "required": false + }, + { + "key": "state", + "label": "Subject Property State", + "type": "string", + "path": "searchCriteria.compAddress.state", + "help": "Two-letter state code, for example AZ.", + "required": false + }, + { + "key": "zip", + "label": "Subject Property ZIP Code", + "type": "string", + "path": "searchCriteria.compAddress.zip", + "required": false + }, + { + "key": "classification", + "label": "Investor Types", + "type": "list", + "path": "searchCriteria.investor.classification.inList", + "help": "For example FLIPPER, LANDLORD, CASH BUYER. Replaces the default (FLIPPER and LANDLORD)." + }, + { + "key": "ownership", + "label": "Ownership", + "type": "choice", + "path": "searchCriteria.investor.ownership", + "choices": [ + { + "value": "current", + "label": "current" + }, + { + "value": "previous", + "label": "previous" + }, + { + "value": "any", + "label": "any" + } + ], + "help": "Which owners of a comparable property qualify. Default any." + }, + { + "key": "sold_within_months", + "label": "Comparable Sales Within (Months)", + "type": "integer", + "path": "searchCriteria.intel.lastSoldDate.withinLastMonths", + "min": 1, + "max": 60, + "help": "From 1 to 60. Default 12." + }, + { + "key": "additional_criteria", + "label": "Additional Criteria (JSON)", + "type": "json", + "path": "searchCriteria", + "merge": true, + "first": true, + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored." + }, + { + "key": "take", + "label": "Max Results", + "type": "integer", + "path": "options.take", + "default": 25, + "min": 1, + "max": 100, + "help": "Whole number from 1 to 100. Default 25." + } + ], + "result": { + "list": "result.investors", + "attach": { + "searchMeta": "result.meta.results", + "warnings": "result.warnings" + } + }, + "sample": { + "id": "inv_a1b2c3d4", + "name": "Evergreen Holdings LLC", + "type": "Company Owned", + "classification": [ + "LANDLORD" + ], + "currentHoldings": { + "totalPropertyCount": 48 + }, + "acquisitions": { + "totalAcquisitionCount": 12 + }, + "mailingAddresses": [ + { + "street": "1200 W Camelback Rd", + "city": "Phoenix", + "state": "AZ", + "zip": "85013", + "propertyCount": 48 + } + ], + "matchedAsCurrentOwner": true, + "searchMeta": { + "resultCount": 25, + "resultsFound": 137 + } + } + }, + { + "key": "investor_properties", + "resource": "investor", + "kind": "search", + "noun": "Investor Property", + "label": "Get Investor Properties", + "description": "Returns the properties of one investor, one result per property. Get the investor ID from Find Investors (Buy Box). Use the \"nextPageCursor\" value of the last result as Page Cursor to read the next page. Billed per property row returned (take sets the maximum cost of a call).", + "http": { + "method": "POST", + "path": "/api/v2/investor/properties" + }, + "fields": [ + { + "key": "investor_id", + "label": "Investor ID", + "type": "string", + "required": true, + "path": "searchCriteria.investorId" + }, + { + "key": "ownership", + "label": "Ownership", + "type": "choice", + "path": "searchCriteria.ownership", + "choices": [ + { + "value": "current", + "label": "current" + }, + { + "value": "previous", + "label": "previous" + }, + { + "value": "any", + "label": "any" + } + ], + "help": "Default current. \"any\" can return many more rows." + }, + { + "key": "include_child_entities", + "label": "Include Child Entities", + "type": "boolean", + "path": "searchCriteria.includeChildEntities" + }, + { + "key": "take", + "label": "Max Results", + "type": "integer", + "path": "options.take", + "default": 25, + "min": 1, + "max": 100, + "help": "Whole number from 1 to 100. Default 25." + }, + { + "key": "page_cursor", + "label": "Page Cursor", + "type": "string", + "path": "options.pageCursor", + "help": "Optional. The nextPageCursor from the previous page, used with the same criteria." + } + ], + "result": { + "list": "result.properties", + "attach": { + "resultsFound": "result.resultsFound", + "nextPageCursor": "result.nextPageCursor" + } + }, + "sample": { + "propertyId": "P-85053-0a1b2c", + "address": { + "street": "3802 W Evans Dr", + "city": "Phoenix", + "state": "AZ", + "zip": "85053", + "latitude": 33.6203, + "longitude": -112.1394 + }, + "fips": "04013", + "apn": "207-14-118", + "ownership": { + "isCurrentOwner": true, + "isPreviousOwner": false + }, + "resultsFound": 48 + } + }, + { + "key": "list_search_sessions", + "resource": "search_session", + "kind": "search", + "noun": "Search Session", + "label": "List Search Sessions", + "description": "Lists your team's search sessions, oldest first. Needs a token with the property search sessions permission. This call does not bill property records.", + "http": { + "method": "GET", + "path": "/api/v2/property/search-sessions" + }, + "fields": [ + { + "key": "take", + "label": "Max Results", + "type": "integer", + "in": "query", + "qs": "take", + "default": 50, + "min": 1, + "max": 100 + }, + { + "key": "skip", + "label": "Skip (Offset)", + "type": "integer", + "in": "query", + "qs": "skip", + "min": 0 + } + ], + "result": { + "list": "results.sessions" + }, + "sample": { + "id": "monthly-leads-az-85020", + "totalDelivered": 25, + "lastRequestAt": "2026-10-28T23:17:54.592Z", + "createdAt": "2026-10-01T16:02:11.120Z" + } + }, + { + "key": "create_search_session", + "resource": "search_session", + "kind": "action", + "noun": "Search Session", + "label": "Create or Advance Search Session", + "description": "Runs a property search inside a persistent session and returns the next properties that session has not delivered yet. The first call creates the session; calling again with the same Session ID continues it. Billed per property record delivered; a session never delivers (or bills) the same property twice. Each call bills up to Max Results records.", + "billed": true, + "http": { + "method": "POST", + "path": "/api/v1/property/search" + }, + "fixed": [ + { + "path": "options.useCursorPagination", + "value": true + } + ], + "fields": [ + { + "key": "session_id", + "label": "Session ID", + "type": "string", + "required": true, + "path": "options.searchSession.id", + "pattern": "^[A-Za-z0-9_-]{1,128}$", + "help": "Your own name for the session: letters, numbers, dash or underscore, up to 128 characters. Reusing the same ID continues the session, so you are never billed twice for the same property. A team can hold 100 sessions." + }, + { + "key": "query", + "label": "Location Query", + "type": "string", + "path": "searchCriteria.query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\"." + }, + { + "key": "quick_lists", + "label": "Quick Lists (Include)", + "type": "list", + "path": "searchCriteria.quickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match ALL selected lists." + }, + { + "key": "or_quick_lists", + "label": "Quick Lists (Match Any)", + "type": "list", + "path": "searchCriteria.orQuickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match AT LEAST ONE selected list." + }, + { + "key": "additional_criteria", + "label": "Additional Criteria (JSON)", + "type": "json", + "path": "searchCriteria", + "merge": true, + "first": true, + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored." + }, + { + "key": "take", + "label": "Max Results", + "type": "integer", + "path": "options.take", + "default": 1, + "min": 1, + "max": 100, + "help": "Whole number from 1 to 100. Default 1. You are billed per record delivered." + } + ], + "result": { + "compose": { + "properties": "results.properties", + "session": "results.searchSessionMeta", + "searchMeta": "results.meta.results" + } + }, + "sample": { + "properties": [ + { + "_id": "09201d3bd3134f9380704014781fb4c0", + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa" + }, + "general": { + "propertyTypeCategory": "Single Family" + }, + "quickLists": { + "vacant": false + } + } + ], + "session": { + "totalDelivered": 1, + "isNewSession": true + }, + "searchMeta": { + "resultCount": 1, + "resultsFound": 1 + } + } + }, + { + "key": "delete_search_session", + "resource": "search_session", + "kind": "action", + "noun": "Search Session", + "label": "Delete Search Session", + "description": "Deletes a search session to free one of your 100 session slots. Deleting a session starts delivery over if you use the same ID again. This call does not bill property records.", + "destructive": true, + "http": { + "method": "DELETE", + "path": "/api/v2/property/search-sessions/{id}" + }, + "fields": [ + { + "key": "id", + "label": "Session ID", + "type": "string", + "required": true, + "in": "path", + "help": "The ID of the session to delete." + } + ], + "dynamic": "new_search_session", + "result": { + "compose": { + "deleted": "results.deleted", + "id": "results.id" + } + }, + "sample": { + "deleted": true, + "id": "monthly-leads-az-85020" + } + }, + { + "key": "list_monitors", + "resource": "monitor", + "kind": "search", + "noun": "Property Monitor", + "label": "List Property Monitors", + "description": "Lists your property monitors. Needs a token with the property subscription permission. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "mockedOnly": true, + "noZapier": true, + "http": { + "method": "GET", + "path": "/api/v2/property-subscription" + }, + "fields": [], + "result": { + "list": "results.subscriptions" + }, + "sample": { + "id": "cjui0E2T1LKxWOD", + "searchCriteria": { + "query": "Phoenix, AZ", + "orQuickLists": [ + "on-market" + ] + }, + "deliveryConfig": { + "type": "kinesis", + "streamName": "my-stream", + "region": "us-west-2" + } + } + }, + { + "key": "get_monitor", + "resource": "monitor", + "kind": "search", + "noun": "Property Monitor", + "label": "Get Property Monitor", + "description": "Returns one property monitor with its search criteria and delivery settings (secrets are masked). Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "mockedOnly": true, + "http": { + "method": "GET", + "path": "/api/v2/property-subscription/{id}" + }, + "fields": [ + { + "key": "id", + "label": "Monitor ID", + "type": "string", + "required": true, + "in": "path", + "help": "The ID of the monitor." + } + ], + "dynamic": "new_monitor", + "result": { + "item": "result" + }, + "sample": { + "id": "cjui0E2T1LKxWOD", + "searchCriteria": { + "query": "Phoenix, AZ", + "orQuickLists": [ + "on-market" + ] + }, + "deliveryConfig": { + "type": "kinesis", + "streamName": "my-stream", + "region": "us-west-2" + } + } + }, + { + "key": "create_monitor", + "resource": "monitor", + "kind": "action", + "noun": "Property Monitor", + "label": "Create Property Monitor (Stream Delivery)", + "description": "Creates a property monitor that streams changes for matching properties to your own AWS Kinesis stream or Azure Event Hub. For webhook delivery use the \"New Property Monitor Event\" trigger, which sets up and removes its own monitor. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "mockedOnly": true, + "http": { + "method": "POST", + "path": "/api/v2/property-subscription" + }, + "fields": [ + { + "key": "query", + "label": "Location Query", + "type": "string", + "path": "searchCriteria.query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\"." + }, + { + "key": "quick_lists", + "label": "Quick Lists (Include)", + "type": "list", + "path": "searchCriteria.quickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match ALL selected lists." + }, + { + "key": "or_quick_lists", + "label": "Quick Lists (Match Any)", + "type": "list", + "path": "searchCriteria.orQuickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match AT LEAST ONE selected list." + }, + { + "key": "additional_criteria", + "label": "Additional Criteria (JSON)", + "type": "json", + "path": "searchCriteria", + "merge": true, + "first": true, + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored." + }, + { + "key": "delivery_type", + "label": "Delivery Type", + "type": "choice", + "required": true, + "path": "deliveryConfig.type", + "choices": [ + { + "value": "kinesis", + "label": "AWS Kinesis" + }, + { + "value": "event-hub", + "label": "Azure Event Hub" + } + ], + "default": "kinesis" + }, + { + "key": "kinesis_stream_name", + "label": "Kinesis Stream Name", + "type": "string", + "path": "deliveryConfig.streamName", + "when": { + "field": "delivery_type", + "equals": "kinesis" + } + }, + { + "key": "kinesis_region", + "label": "Kinesis AWS Region", + "type": "string", + "path": "deliveryConfig.region", + "when": { + "field": "delivery_type", + "equals": "kinesis" + }, + "help": "For example us-west-2." + }, + { + "key": "kinesis_access_key_id", + "label": "Kinesis IAM Access Key ID", + "type": "string", + "path": "deliveryConfig.iamAccessKeyId", + "when": { + "field": "delivery_type", + "equals": "kinesis" + } + }, + { + "key": "kinesis_secret_access_key", + "label": "Kinesis IAM Secret Access Key", + "type": "password", + "path": "deliveryConfig.iamSecretAccessKey", + "when": { + "field": "delivery_type", + "equals": "kinesis" + } + }, + { + "key": "event_hub_name", + "label": "Event Hub Name", + "type": "string", + "path": "deliveryConfig.eventHub.eventHubName", + "when": { + "field": "delivery_type", + "equals": "event-hub" + } + }, + { + "key": "event_hub_connection_string", + "label": "Event Hub Connection String", + "type": "password", + "path": "deliveryConfig.eventHub.connectionString", + "when": { + "field": "delivery_type", + "equals": "event-hub" + }, + "help": "Use this, or the namespace and service principal fields below." + }, + { + "key": "event_hub_namespace", + "label": "Event Hub Fully Qualified Namespace", + "type": "string", + "path": "deliveryConfig.eventHub.fullyQualifiedNamespace", + "when": { + "field": "delivery_type", + "equals": "event-hub" + } + }, + { + "key": "event_hub_client_id", + "label": "Event Hub Client ID", + "type": "string", + "path": "deliveryConfig.eventHub.clientId", + "when": { + "field": "delivery_type", + "equals": "event-hub" + } + }, + { + "key": "event_hub_tenant_id", + "label": "Event Hub Tenant ID", + "type": "string", + "path": "deliveryConfig.eventHub.tenantId", + "when": { + "field": "delivery_type", + "equals": "event-hub" + } + }, + { + "key": "event_hub_client_secret", + "label": "Event Hub Client Secret", + "type": "password", + "path": "deliveryConfig.eventHub.clientSecret", + "when": { + "field": "delivery_type", + "equals": "event-hub" + } + } + ], + "result": { + "compose": { + "subscriptionId": "result.subscriptionId" + } + }, + "sample": { + "subscriptionId": "FXeLSlemePGGhvt" + } + }, + { + "key": "delete_monitor", + "resource": "monitor", + "kind": "action", + "noun": "Property Monitor", + "label": "Delete Property Monitor", + "description": "Deletes a property monitor so it stops delivering events. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "destructive": true, + "mockedOnly": true, + "http": { + "method": "DELETE", + "path": "/api/v2/property-subscription/{id}" + }, + "fields": [ + { + "key": "id", + "label": "Monitor ID", + "type": "string", + "required": true, + "in": "path", + "help": "The ID of the monitor to delete." + } + ], + "dynamic": "new_monitor", + "result": { + "static": { + "deleted": true + }, + "echo": { + "id": "id" + } + }, + "sample": { + "deleted": true, + "id": "FXeLSlemePGGhvt" + } + } + ], + "triggers": [ + { + "key": "new_search_session", + "mode": "poll", + "noun": "Search Session", + "label": "New Search Session", + "description": "Triggers when a new search session appears on your team. Needs a token with the property search sessions permission. This call does not bill property records.", + "http": { + "method": "GET", + "path": "/api/v2/property/search-sessions" + }, + "fixedQuery": { + "take": 100 + }, + "fields": [], + "result": { + "list": "results.sessions" + }, + "dedupe": "id", + "sortDesc": "createdAt", + "sample": { + "id": "monthly-leads-az-85020", + "totalDelivered": 25, + "lastRequestAt": "2026-10-28T23:17:54.592Z", + "createdAt": "2026-10-01T16:02:11.120Z" + } + }, + { + "key": "new_monitor", + "mode": "poll", + "noun": "Property Monitor", + "label": "New Property Monitor", + "description": "Triggers when a new property monitor is created on your team. Needs a token with the property subscription permission. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event).", + "mockedOnly": true, + "http": { + "method": "GET", + "path": "/api/v2/property-subscription" + }, + "fields": [], + "result": { + "list": "results.subscriptions" + }, + "dedupe": "id", + "sample": { + "id": "cjui0E2T1LKxWOD", + "searchCriteria": { + "query": "Phoenix, AZ", + "orQuickLists": [ + "on-market" + ] + }, + "deliveryConfig": { + "type": "kinesis", + "streamName": "my-stream", + "region": "us-west-2" + } + } + }, + { + "key": "new_session_property", + "mode": "poll", + "billed": true, + "noun": "Session Property", + "label": "New Property in Search Session (Billed)", + "description": "Triggers when a search session delivers a property that it has not delivered before. Every check runs the search and bills each property delivered, up to Max Results per check, and a property is never billed twice within the same Session ID. Billed per property record delivered; a session never delivers (or bills) the same property twice. Use a low Max Results while you test.", + "http": { + "method": "POST", + "path": "/api/v1/property/search" + }, + "fixed": [ + { + "path": "options.useCursorPagination", + "value": true + } + ], + "fields": [ + { + "key": "session_id", + "label": "Session ID", + "type": "string", + "required": true, + "path": "options.searchSession.id", + "pattern": "^[A-Za-z0-9_-]{1,128}$", + "help": "Your own name for the session: letters, numbers, dash or underscore, up to 128 characters. Reusing the same ID continues the session, so you are never billed twice for the same property. A team can hold 100 sessions." + }, + { + "key": "query", + "label": "Location Query", + "type": "string", + "path": "searchCriteria.query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\"." + }, + { + "key": "quick_lists", + "label": "Quick Lists (Include)", + "type": "list", + "path": "searchCriteria.quickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match ALL selected lists." + }, + { + "key": "or_quick_lists", + "label": "Quick Lists (Match Any)", + "type": "list", + "path": "searchCriteria.orQuickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match AT LEAST ONE selected list." + }, + { + "key": "additional_criteria", + "label": "Additional Criteria (JSON)", + "type": "json", + "path": "searchCriteria", + "merge": true, + "first": true, + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored." + }, + { + "key": "take", + "label": "Max Results", + "type": "integer", + "path": "options.take", + "default": 25, + "min": 1, + "max": 100, + "help": "Whole number from 1 to 100. Default 25. You are billed per property delivered on each check." + } + ], + "result": { + "list": "results.properties", + "idFrom": { + "id": "_id" + }, + "attach": { + "searchMeta": "results.meta.results" + } + }, + "dedupe": "id", + "sample": { + "id": "09201d3bd3134f9380704014781fb4c0", + "_id": "09201d3bd3134f9380704014781fb4c0", + "address": { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa" + }, + "general": { + "propertyTypeCategory": "Single Family" + }, + "quickLists": { + "vacant": false + } + } + }, + { + "key": "monitor_event", + "mode": "hook", + "noun": "Property Monitor Event", + "label": "New Property Monitor Event", + "description": "Triggers when BatchData reports that a property matching your criteria was added to or removed from a monitor, or changed (instant). Turning the Zap or scenario on creates a monitor that delivers to this trigger's own hook URL; turning it off deletes that monitor. Each event carries the property ID and change details; turn on \"Include Property Record\" to also look up the full property record. Monitors and the events they deliver are billed according to your BatchData plan; contact support@batchservice.com for pricing. Limits: up to 5 monitors per team, a monitor cannot be edited after it is created (delete and recreate it), a new monitor can take up to 7 days to start delivering, and delivery is best effort (4 attempts per event). The optional property lookup is billed per event, per matched property.", + "mockedOnly": true, + "http": { + "method": "POST", + "path": "/api/v2/property-subscription", + "deletePath": "/api/v2/property-subscription/{id}", + "idPath": "result.subscriptionId" + }, + "deliveryType": "webhook", + "hydrate": { + "path": "/api/v1/property/lookup/all-attributes", + "idField": "propertyId", + "listPath": "results.properties", + "into": "property" + }, + "fields": [ + { + "key": "query", + "label": "Location Query", + "type": "string", + "path": "searchCriteria.query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\"." + }, + { + "key": "quick_lists", + "label": "Quick Lists (Include)", + "type": "list", + "path": "searchCriteria.quickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match ALL selected lists." + }, + { + "key": "or_quick_lists", + "label": "Quick Lists (Match Any)", + "type": "list", + "path": "searchCriteria.orQuickLists", + "choices": [ + { + "value": "absentee-owner", + "label": "absentee-owner" + }, + { + "value": "active-auction", + "label": "active-auction" + }, + { + "value": "active-listing", + "label": "active-listing" + }, + { + "value": "canceled-listing", + "label": "canceled-listing" + }, + { + "value": "cash-buyer", + "label": "cash-buyer" + }, + { + "value": "corporate-owned", + "label": "corporate-owned" + }, + { + "value": "expired-listing", + "label": "expired-listing" + }, + { + "value": "failed-listing", + "label": "failed-listing" + }, + { + "value": "fix-and-flip", + "label": "fix-and-flip" + }, + { + "value": "free-and-clear", + "label": "free-and-clear" + }, + { + "value": "for-sale-by-owner", + "label": "for-sale-by-owner" + }, + { + "value": "has-hoa", + "label": "has-hoa" + }, + { + "value": "has-hoa-fees", + "label": "has-hoa-fees" + }, + { + "value": "high-equity", + "label": "high-equity" + }, + { + "value": "inherited", + "label": "inherited" + }, + { + "value": "involuntary-lien", + "label": "involuntary-lien" + }, + { + "value": "in-state-absentee-owner", + "label": "in-state-absentee-owner" + }, + { + "value": "listed-below-market-price", + "label": "listed-below-market-price" + }, + { + "value": "low-equity", + "label": "low-equity" + }, + { + "value": "mailing-address-vacant", + "label": "mailing-address-vacant" + }, + { + "value": "notice-of-default", + "label": "notice-of-default" + }, + { + "value": "notice-of-lis-pendens", + "label": "notice-of-lis-pendens" + }, + { + "value": "notice-of-sale", + "label": "notice-of-sale" + }, + { + "value": "on-market", + "label": "on-market" + }, + { + "value": "out-of-state-absentee-owner", + "label": "out-of-state-absentee-owner" + }, + { + "value": "out-of-state-owner", + "label": "out-of-state-owner" + }, + { + "value": "owner-occupied", + "label": "owner-occupied" + }, + { + "value": "pending-listing", + "label": "pending-listing" + }, + { + "value": "preforeclosure", + "label": "preforeclosure" + }, + { + "value": "recently-sold", + "label": "recently-sold" + }, + { + "value": "same-property-and-mailing-address", + "label": "same-property-and-mailing-address" + }, + { + "value": "senior-owner", + "label": "senior-owner" + }, + { + "value": "tax-default", + "label": "tax-default" + }, + { + "value": "tired-landlord", + "label": "tired-landlord" + }, + { + "value": "trust-owned", + "label": "trust-owned" + }, + { + "value": "unknown-equity", + "label": "unknown-equity" + }, + { + "value": "vacant", + "label": "vacant" + }, + { + "value": "vacant-lot", + "label": "vacant-lot" + } + ], + "help": "Properties must match AT LEAST ONE selected list." + }, + { + "key": "additional_criteria", + "label": "Additional Criteria (JSON)", + "type": "json", + "path": "searchCriteria", + "merge": true, + "first": true, + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored." + }, + { + "key": "include_property", + "label": "Include Property Record (Billed)", + "type": "boolean", + "local": true, + "default": false, + "help": "Off by default. When on, each event also runs a property lookup and adds the full record as \"property\". The lookup is billed per matched property." + } + ], + "dedupe": "id", + "sample": { + "id": "evt_9c1f2a", + "eventId": "evt_9c1f2a", + "propertyId": "09201d3bd3134f9380704014781fb4c0", + "parcelHash": "a1b2c3d4e5f60718293a4b5c6d7e8f90", + "addressHash": "90a01b18e3834e349a8d1d07d7e086ba", + "addedToSubscriptionIds": [ + "FXeLSlemePGGhvt" + ], + "removedFromSubscriptionIds": [], + "propertyChangedAt": "2026-10-28T22:10:41.000Z", + "sentAt": "2026-10-28T22:10:45.000Z" + } + } + ], + "apiRequest": { + "key": "api_request", + "resource": "custom", + "noun": "API Response", + "label": "API Request (Advanced)", + "description": "Calls any BatchData API path with your token. Billing follows the endpoint you call. Account balance and billing endpoints, asynchronous (callback) endpoints and monitor creation are not available here.", + "methods": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "guard": { + "mustStartWith": "/api/", + "blockedPathContains": [ + "wallet" + ], + "blockedPathSegments": [ + "async" + ], + "blockedWrites": [ + { + "pathContains": "property-subscription", + "methods": [ + "POST", + "PUT", + "PATCH" + ] + } + ] + }, + "sample": { + "statusCode": 200, + "body": { + "status": { + "code": 200, + "text": "OK" + } + } + } + } +} diff --git a/scripts/catalog.sha256 b/scripts/catalog.sha256 new file mode 100644 index 0000000..3648287 --- /dev/null +++ b/scripts/catalog.sha256 @@ -0,0 +1 @@ +0fa2900e91bdbe71e5f6ba42802bea92efa56f93cfa488038d4be03314f3635f diff --git a/scripts/constants.json b/scripts/constants.json new file mode 100644 index 0000000..e48a346 --- /dev/null +++ b/scripts/constants.json @@ -0,0 +1,58 @@ +{ + "QUICKLISTS": [ + "absentee-owner", + "active-auction", + "active-listing", + "canceled-listing", + "cash-buyer", + "corporate-owned", + "expired-listing", + "failed-listing", + "fix-and-flip", + "free-and-clear", + "for-sale-by-owner", + "has-hoa", + "has-hoa-fees", + "high-equity", + "inherited", + "involuntary-lien", + "in-state-absentee-owner", + "listed-below-market-price", + "low-equity", + "mailing-address-vacant", + "notice-of-default", + "notice-of-lis-pendens", + "notice-of-sale", + "on-market", + "out-of-state-absentee-owner", + "out-of-state-owner", + "owner-occupied", + "pending-listing", + "preforeclosure", + "recently-sold", + "same-property-and-mailing-address", + "senior-owner", + "tax-default", + "tired-landlord", + "trust-owned", + "unknown-equity", + "vacant", + "vacant-lot" + ], + "LOOKUP_DATASETS": [ + "basic", + "comps", + "batchrank", + "contact", + "core", + "deed", + "demographic", + "foreclosure", + "listing", + "mortgage-liens", + "owner", + "permit", + "quicklist", + "valuation" + ] +} diff --git a/scripts/generate.mjs b/scripts/generate.mjs new file mode 100644 index 0000000..69b4d34 --- /dev/null +++ b/scripts/generate.mjs @@ -0,0 +1,366 @@ +import fs from 'node:fs'; +import { root, write, toInterface } from './lib.mjs'; +import * as P from './params.mjs'; +import { catalogModules, catalogGroups, webhooks, rpcs } from './catalog-modules.mjs'; + +const fx = (n) => JSON.parse(fs.readFileSync(`${root}/../fixtures/${n}.json`, 'utf8')); +const ue = (e) => `ifempty(${e}, undefined)`; +const u = (e) => `{{${ue(e)}}}`; + +const BILL = { + search: 'Billed per record returned.', + lookup: 'Billed per matched property.', + skip: 'Billed per matched property.', + address: 'Billed per address.', + phone: 'Billed per phone number.', +}; +const PHONE_NOTE = ' Results are data only. You are responsible for TCPA and Do Not Call compliance.'; + +const skipBody = (path, iterate) => ({ + url: path, + method: 'POST', + body: { + requests: [ + { + propertyAddress: `{{if(parameters.findBy == 'address', parameters.propertyAddress, undefined)}}`, + apn: `{{if(parameters.findBy == 'apn', ${ue('parameters.apn')}, undefined)}}`, + countyFipsCode: `{{if(parameters.findBy == 'apn', ${ue('parameters.countyFipsCode')}, undefined)}}`, + county: `{{if(parameters.findBy == 'apn', ${ue('parameters.county')}, undefined)}}`, + state: `{{if(parameters.findBy == 'apn', ${ue('parameters.state')}, undefined)}}`, + name: '{{ifempty(parameters.name, undefined)}}', + mailingAddress: '{{ifempty(parameters.mailingAddress, undefined)}}', + requestId: u('parameters.requestId'), + }, + ], + options: { + includeTCPABlacklistedPhones: '{{if(parameters.includeTcpaFlaggedPhones == true, true, false)}}', + prioritizeMobilePhones: u('parameters.prioritizeMobilePhones'), + }, + }, + response: { iterate, output: '{{item}}' }, +}); + +// Blocks account-balance (wallet) paths, asynchronous callback endpoints, path tricks and monitor +// creation. The same rules exist in the Zapier and n8n integrations (catalog apiRequest.guard). +const lc = 'lower(parameters.url)'; +const API_GUARD = [ + `contains(${lc}, 'wallet')`, + `contains(${lc}, 'async')`, + `contains(${lc}, '..')`, + `contains(${lc}, '%')`, + `contains(${lc}, '?')`, + `contains(${lc}, '://')`, + `contains(lower(ifempty(parameters.body, '')), 'webhook')`, + `contains(lower(ifempty(parameters.body, '')), 'u00')`, + `contains(${lc}, '//')`, + `contains(${lc}, '#')`, + `(contains(${lc}, 'property-subscription') && (parameters.method == 'POST' || parameters.method == 'PUT' || parameters.method == 'PATCH'))`, +].join(' || '); + +const modules = { + searchProperties: { + moduleType: 'search', + label: 'Search Properties', + description: `Finds properties that match your filters. ${BILL.search} A Max results value of 0 is billed as 1 record. One call returns at most 100 records.`, + communication: { + url: '/v1/property/search', + method: 'POST', + body: { + searchCriteria: "{{omit(parameters, 'take', 'skip')}}", + options: { take: '{{ifempty(parameters.take, 25)}}', skip: u('parameters.skip') }, + }, + response: { + iterate: '{{body.results.properties}}', + output: { '{{...}}': '{{item}}', searchMeta: '{{body.results.meta.results}}' }, + }, + }, + params: P.searchParams, + sampleFrom: () => { + const f = fx('property-search'); + return { ...f.results.properties[0], searchMeta: f.results.meta.results }; + }, + }, + lookupProperty: { + moduleType: 'action', + label: 'Get Property Details', + description: `Returns property data for one address or identifier. ${BILL.lookup} Skip trace, if included, is billed separately.`, + communication: { + url: '/v1/property/lookup/all-attributes', + method: 'POST', + body: { + requests: [ + { + address: "{{if(parameters.identifyBy == 'address', parameters.address, undefined)}}", + propertyId: `{{if(parameters.identifyBy == 'propertyId', ${ue('parameters.propertyId')}, undefined)}}`, + hash: `{{if(parameters.identifyBy == 'hash', ${ue('parameters.hash')}, undefined)}}`, + apn: `{{if(parameters.identifyBy == 'apn', ${ue('parameters.apn')}, undefined)}}`, + countyFipsCode: `{{if(parameters.identifyBy == 'apn', ${ue('parameters.countyFipsCode')}, undefined)}}`, + requestId: u('parameters.requestId'), + }, + ], + options: { + skipTrace: '{{if(parameters.includeSkipTrace == true, true, undefined)}}', + datasets: '{{if(length(parameters.datasets) > 0, parameters.datasets, undefined)}}', + }, + }, + response: { output: '{{first(body.results.properties)}}' }, + }, + params: P.lookupParams, + sampleFrom: () => fx('property-lookup-all-attributes').results.properties[0], + }, + skipTraceV1: { + moduleType: 'search', + label: 'Skip Trace Owner (V1)', + description: `Finds owner contact details for a property, one result per person. ${BILL.skip} Phone numbers flagged as TCPA litigators are removed unless you turn that option on.${PHONE_NOTE}`, + communication: skipBody('/v1/property/skip-trace', '{{body.results.persons}}'), + params: P.skipTraceV1Params, + sampleFrom: () => fx('property-skip-trace').results.persons[0], + }, + skipTraceV3: { + moduleType: 'search', + label: 'Skip Trace Owner (V3)', + description: `Finds owner contact details using the V3 skip trace, one result per property with a list of persons. ${BILL.skip} Phone numbers flagged as TCPA litigators are removed unless you turn that option on.${PHONE_NOTE}`, + communication: skipBody('/v3/property/skip-trace', '{{body.result.data}}'), + params: P.skipTraceV3Params, + sampleFrom: () => fx('v3-property-skip-trace').result.data[0], + }, + verifyAddress: { + moduleType: 'action', + label: 'Verify Address', + description: `Standardizes and validates one US address. ${BILL.address}`, + communication: { + url: '/v1/address/verify', + method: 'POST', + body: { requests: [{ street: '{{parameters.street}}', city: u('parameters.city'), state: u('parameters.state'), zip: u('parameters.zip') }] }, + response: { output: '{{first(body.results.addresses)}}' }, + }, + params: P.verifyAddressParams, + sampleFrom: () => fx('address-verify').results.addresses[0], + }, + verifyPhones: { + moduleType: 'search', + label: 'Verify Phone Numbers', + description: `Checks whether phone numbers are reachable and returns carrier, DNC and TCPA flags, one result per number. ${BILL.phone}${PHONE_NOTE}`, + communication: { + url: '/v1/phone/verification', + method: 'POST', + body: { requests: '{{parameters.phoneNumbers}}' }, + response: { iterate: '{{body.results.phoneNumbers}}', output: '{{item}}' }, + }, + params: P.phoneParams, + sampleFrom: () => fx('phone-verification').results.phoneNumbers[0], + }, + checkDnc: { + moduleType: 'search', + label: 'Check Do Not Call', + description: `Checks phone numbers against the Do Not Call registry, one result per number. ${BILL.phone}${PHONE_NOTE}`, + communication: { + url: '/v1/phone/dnc', + method: 'POST', + body: { requests: '{{parameters.phoneNumbers}}' }, + response: { iterate: '{{body.results.phoneNumbers}}', output: '{{item}}' }, + }, + params: P.phoneParams, + sampleFrom: () => fx('phone-dnc').results.phoneNumbers[0], + }, + checkTcpa: { + moduleType: 'search', + label: 'Check TCPA Litigator', + description: `Checks phone numbers against the TCPA litigator list, one result per number. ${BILL.phone}${PHONE_NOTE}`, + communication: { + url: '/v1/phone/tcpa', + method: 'POST', + body: { requests: '{{parameters.phoneNumbers}}' }, + response: { iterate: '{{body.results.phoneNumbers}}', output: '{{item}}' }, + }, + params: P.phoneParams, + sampleFrom: () => fx('phone-tcpa').results.phoneNumbers[0], + }, + makeAnApiCall: { + moduleType: 'universal', + label: 'Make an API Call', + description: 'Performs an authorized call to any BatchData API path. Billing follows the endpoint you call. Account balance (wallet) endpoints, asynchronous (callback) endpoints and monitor creation are not available here; use the dedicated modules for those.', + communication: { + url: `/{{if(${API_GUARD}, 'blocked/not-available', trim(parameters.url))}}`, + method: '{{parameters.method}}', + headers: { '{{...}}': "{{toCollection(parameters.headers, 'key', 'value')}}" }, + qs: { '{{...}}': `{{if(${API_GUARD}, undefined, toCollection(parameters.qs, 'key', 'value'))}}` }, + body: `{{if(${API_GUARD} || length(ifempty(parameters.body, '')) == 0, undefined, parseJSON(parameters.body))}}`, + type: 'json', + response: { output: { statusCode: '{{statusCode}}', headers: '{{headers}}', body: '{{body}}' } }, + }, + params: P.apiCallParams, + sampleFrom: () => ({ statusCode: 200, headers: {}, body: {} }), + }, +}; + +Object.assign(modules, catalogModules); + +const baseGroups = [ + { label: 'Properties', modules: ['searchProperties', 'lookupProperty'] }, + { label: 'Owner contact', modules: ['skipTraceV1', 'skipTraceV3'] }, + { label: 'Addresses', modules: ['verifyAddress'] }, + { label: 'Phone numbers', modules: ['verifyPhones', 'checkDnc', 'checkTcpa'] }, + { label: 'Other', modules: ['makeAnApiCall'] }, +]; +// Same-named groups are merged; the generic API call stays last. +const groups = []; +for (const g of [...baseGroups.filter((x) => x.label !== 'Other'), ...catalogGroups, ...baseGroups.filter((x) => x.label === 'Other')]) { + const hit = groups.find((x) => x.label === g.label); + if (hit) hit.modules.push(...g.modules); + else groups.push({ label: g.label, modules: [...g.modules] }); +} + +const moduleComponents = {}; +const idMap = []; +for (const [key, m] of Object.entries(modules)) { + const dir = `modules/${key}/${key}`; + const isTrigger = m.moduleType.includes('trigger'); + const files = { communication: `${dir}.communication.iml.json` }; + if (isTrigger) { + if (m.moduleType === 'trigger') files.staticParams = `${dir}.static-params.iml.json`; + } else { + files.mappableParams = `${dir}.mappable-params.iml.json`; + } + files.interface = `${dir}.interface.iml.json`; + files.samples = `${dir}.samples.iml.json`; + const sample = m.sampleFrom(); + write(files.communication, m.communication); + if (files.mappableParams) write(files.mappableParams, m.params); + if (files.staticParams) write(files.staticParams, m.staticParams ?? []); + write(files.samples, sample); + write( + files.interface, + m.moduleType === 'universal' + ? [ + { name: 'statusCode', type: 'number', label: 'Status code' }, + { name: 'headers', type: 'collection', label: 'Headers', spec: [] }, + { name: 'body', type: 'any', label: 'Body' }, + ] + : toInterface(sample), + ); + moduleComponents[key] = { + moduleType: m.moduleType, + label: m.label, + description: m.description, + ...(m.moduleType === 'universal' || isTrigger ? {} : { actionCrud: m.actionCrud ?? 'read' }), + connection: 'batchdata', + ...(m.webhook ? { webhook: m.webhook } : {}), + codeFiles: files, + }; + idMap.push({ local: key, remote: key }); +} + +const webhookComponents = {}; +const webhookIdMap = []; +for (const [key, w] of Object.entries(webhooks)) { + const dir = `webhooks/${key}/${key}`; + const files = { + communication: `${dir}.communication.iml.json`, + params: `${dir}.params.iml.json`, + attach: `${dir}.attach.iml.json`, + detach: `${dir}.detach.iml.json`, + }; + write(files.communication, w.communication); + write(files.params, w.params); + write(files.attach, w.attach); + write(files.detach, w.detach); + webhookComponents[key] = { label: w.label, webhookType: w.webhookType, connection: 'batchdata', codeFiles: files }; + webhookIdMap.push({ local: key, remote: key }); +} + +const rpcComponents = {}; +const rpcIdMap = []; +for (const [key, r] of Object.entries(rpcs)) { + const dir = `rpcs/${key}/${key}`; + const files = { communication: `${dir}.communication.iml.json`, params: `${dir}.params.iml.json` }; + write(files.communication, r.communication); + write(files.params, r.params); + rpcComponents[key] = { label: r.label, connection: 'batchdata', codeFiles: files }; + rpcIdMap.push({ local: key, remote: key }); +} + +write('modules/groups.json', groups); +write('general/common.json', {}); +write('general/base.iml.json', { + baseUrl: 'https://api.batchdata.com/api', + headers: { Authorization: 'Bearer {{connection.apiKey}}', 'Content-Type': 'application/json' }, + response: { + error: { + message: "[{{statusCode}}] {{ifempty(body.status.message, ifempty(body.status.text, 'The BatchData API returned an unexpected error.'))}}", + type: 'RuntimeError', + 400: { type: 'DataError', message: "[400] {{ifempty(body.status.message, 'The request was rejected. Check the values you entered.')}}" }, + 401: { type: 'InvalidAccessTokenError', message: 'Your BatchData API token is invalid or expired. Create a new connection with a valid token.' }, + 403: { type: 'RuntimeError', message: 'Your BatchData API token does not have access to this data. Contact support@batchservice.com to enable it.' }, + 429: { type: 'RateLimitError', message: 'The BatchData rate limit was reached. Wait a moment and try again.' }, + }, + }, + log: { sanitize: ['request.headers.authorization'] }, +}); + +write('connections/batchdata/batchdata.params.iml.json', [ + { + name: 'apiKey', + type: 'password', + label: 'API token', + required: true, + help: 'Create an API token in your BatchData account and paste it here. Tokens are only available on plans with API access.', + }, +]); +write('connections/batchdata/batchdata.communication.iml.json', { + url: 'https://api.batchdata.com/api/v1/address/verify', + method: 'POST', + headers: { Authorization: 'Bearer {{parameters.apiKey}}', 'Content-Type': 'application/json' }, + body: { requests: [] }, + response: { + valid: '{{statusCode == 400 || (statusCode >= 200 && statusCode < 300)}}', + data: { apiKey: '{{parameters.apiKey}}' }, + error: { + message: "[{{statusCode}}] {{ifempty(body.status.message, 'Could not verify the API token.')}}", + 401: { type: 'InvalidAccessTokenError', message: 'Invalid API token. Copy a fresh token from your BatchData account.' }, + 403: { type: 'RuntimeError', message: 'This token cannot use the BatchData API. Contact support@batchservice.com.' }, + }, + }, + log: { sanitize: ['request.headers.authorization'] }, +}); + +write('makecomapp.json', { + fileVersion: 1, + generalCodeFiles: { + base: 'general/base.iml.json', + common: 'general/common.json', + readme: 'README.md', + groups: 'modules/groups.json', + }, + components: { + connection: { + batchdata: { + label: 'BatchData', + codeFiles: { + communication: 'connections/batchdata/batchdata.communication.iml.json', + params: 'connections/batchdata/batchdata.params.iml.json', + }, + }, + }, + module: moduleComponents, + webhook: webhookComponents, + rpc: rpcComponents, + }, + origins: [ + { + label: 'Make (set your zone)', + baseUrl: 'https://eu1.make.com/api', + appId: 'batchdata', + appVersion: 1, + apikeyFile: '.secrets/apikey', + idMapping: { + connection: [{ local: 'batchdata', remote: 'batchdata' }], + module: idMap, + function: [], + rpc: rpcIdMap, + webhook: webhookIdMap, + }, + }, + ], +}); +console.log('generated', Object.keys(modules).length, 'modules'); diff --git a/scripts/lib.mjs b/scripts/lib.mjs new file mode 100644 index 0000000..c35cc3b --- /dev/null +++ b/scripts/lib.mjs @@ -0,0 +1,43 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); + +export const write = (rel, data) => { + const file = path.join(root, rel); + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, typeof data === 'string' ? data : `${JSON.stringify(data, null, 2)}\n`); +}; + +const words = (name) => + name + .replace(/^_+/, '') + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') + .replace(/_/g, ' ') + .toLowerCase() + .replace(/^./, (c) => c.toUpperCase()); + +// Turn a sample value into a Make interface (type/label/spec) list. +export const toInterface = (value) => { + const out = []; + for (const [name, v] of Object.entries(value)) { + const label = words(name); + if (Array.isArray(v)) { + const first = v.find((x) => x !== null && x !== undefined); + if (first && typeof first === 'object' && !Array.isArray(first)) { + out.push({ name, type: 'array', label, spec: toInterface(first) }); + } else { + out.push({ name, type: 'array', label, spec: { type: typeof first === 'number' ? 'number' : 'text' } }); + } + } else if (v && typeof v === 'object') { + out.push({ name, type: 'collection', label, spec: toInterface(v) }); + } else if (typeof v === 'number') { + out.push({ name, type: 'number', label }); + } else if (typeof v === 'boolean') { + out.push({ name, type: 'boolean', label }); + } else { + out.push({ name, type: 'text', label }); + } + } + return out; +}; diff --git a/scripts/params.mjs b/scripts/params.mjs new file mode 100644 index 0000000..14f4cb8 --- /dev/null +++ b/scripts/params.mjs @@ -0,0 +1,216 @@ +import fs from 'node:fs'; +import { root } from './lib.mjs'; + +const C = JSON.parse(fs.readFileSync(`${root}/scripts/constants.json`, 'utf8')); + +const opt = (v) => ({ label: v, value: v }); +const num = (name, label) => ({ name, type: 'number', label }); +const txt = (name, label, extra = {}) => ({ name, type: 'text', label, ...extra }); +const range = (name, label) => ({ + name, + type: 'collection', + label, + spec: [num('min', 'Minimum'), num('max', 'Maximum')], +}); +const addressSpec = () => [ + txt('street', 'Street'), + txt('city', 'City'), + txt('state', 'State', { help: 'Two-letter state code, for example AZ.' }), + txt('zip', 'ZIP code'), +]; + +export const searchParams = [ + txt('query', 'Location or text query', { + help: 'City, county, ZIP code or address text to search in. Leave empty to rely on the filters below.', + }), + { + name: 'quickLists', + type: 'array', + label: 'Quick lists (all must match)', + help: 'Prebuilt lead lists. Choose a "not-" entry to exclude a list.', + spec: { type: 'select', options: [...C.QUICKLISTS.map(opt), ...C.QUICKLISTS.map((q) => opt(`not-${q}`))] }, + }, + { + name: 'orQuickLists', + type: 'array', + label: 'Quick lists (any may match)', + spec: { type: 'select', options: C.QUICKLISTS.map(opt) }, + }, + { + name: 'building', + type: 'collection', + label: 'Building', + spec: [ + range('bedroomCount', 'Bedrooms'), + range('bathroomCount', 'Bathrooms'), + range('yearBuilt', 'Year built'), + range('livingAreaSquareFeet', 'Living area (sq ft)'), + ], + }, + { + name: 'general', + type: 'collection', + label: 'Property type', + spec: [ + { name: 'propertyTypeCategory', type: 'collection', label: 'Property type category', spec: [txt('equals', 'Equals', { help: 'For example Single Family, Multi Family, Condo.' })] }, + { name: 'propertyTypeDetail', type: 'collection', label: 'Property type detail', spec: [txt('equals', 'Equals')] }, + ], + }, + { name: 'lot', type: 'collection', label: 'Lot', spec: [range('lotSizeAcres', 'Lot size (acres)')] }, + { + name: 'valuation', + type: 'collection', + label: 'Valuation', + spec: [range('estimatedValue', 'Estimated value'), range('equityPercent', 'Equity percent')], + }, + { + name: 'sale', + type: 'collection', + label: 'Sale', + spec: [ + { + name: 'lastSaleDate', + type: 'collection', + label: 'Last sale date', + spec: [txt('minDate', 'From (YYYY-MM-DD)'), txt('maxDate', 'To (YYYY-MM-DD)')], + }, + ], + }, + { + name: 'owner', + type: 'collection', + label: 'Owner', + spec: [ + { + name: 'ownerOccupied', + type: 'collection', + label: 'Owner occupied', + spec: [{ name: 'equals', type: 'boolean', label: 'Equals' }], + }, + ], + }, + { + name: 'take', + type: 'uinteger', + label: 'Max results', + default: 25, + validate: { max: 100 }, + help: 'Up to 100 per run. You are billed per record returned, and a value of 0 is billed as 1 record.', + }, + { name: 'skip', type: 'uinteger', label: 'Skip', help: 'Number of records to skip, for paging.' }, +]; + +export const lookupParams = [ + { + name: 'identifyBy', + type: 'select', + label: 'Identify property by', + required: true, + default: 'address', + options: [ + { label: 'Address', value: 'address' }, + { label: 'Property ID', value: 'propertyId' }, + { label: 'Address hash', value: 'hash' }, + { label: 'APN and county FIPS code', value: 'apn' }, + ], + help: 'Only the identifier chosen here is sent. The other identifier fields are ignored.', + }, + { name: 'address', type: 'collection', label: 'Address', spec: addressSpec() }, + txt('propertyId', 'Property ID'), + txt('hash', 'Address hash'), + txt('apn', 'APN'), + txt('countyFipsCode', 'County FIPS code'), + { + name: 'datasets', + type: 'array', + label: 'Datasets', + help: 'Extra data to include. "basic" and "core" cannot be combined. Leave empty for the default set.', + spec: { type: 'select', options: C.LOOKUP_DATASETS.map(opt) }, + }, + { + name: 'includeSkipTrace', + type: 'boolean', + label: 'Include skip trace', + help: 'Skip trace is billed separately for each matched record.', + }, + txt('requestId', 'Request ID', { help: 'Optional value returned with the result so you can match it later.' }), +]; + +const skipTraceParams = [ + { + name: 'findBy', + type: 'select', + label: 'Find owner by', + required: true, + default: 'address', + options: [ + { label: 'Property address', value: 'address' }, + { label: 'APN', value: 'apn' }, + ], + }, + { name: 'propertyAddress', type: 'collection', label: 'Property address', spec: addressSpec() }, + txt('apn', 'APN'), + txt('countyFipsCode', 'County FIPS code'), + txt('county', 'County'), + txt('state', 'State (for APN)'), + { name: 'name', type: 'collection', label: 'Owner name (optional)', spec: [txt('first', 'First name'), txt('last', 'Last name')] }, + { name: 'mailingAddress', type: 'collection', label: 'Owner mailing address (optional)', spec: addressSpec() }, + txt('requestId', 'Request ID'), + { + name: 'includeTcpaFlaggedPhones', + type: 'boolean', + label: 'Include phone numbers flagged as TCPA litigators', + default: false, + help: 'Off by default: flagged numbers are removed. Turn on only if your compliance process allows it.', + }, + { name: 'prioritizeMobilePhones', type: 'boolean', label: 'Prioritize mobile phones' }, +]; +export const skipTraceV1Params = skipTraceParams; +export const skipTraceV3Params = skipTraceParams; + +export const verifyAddressParams = [ + txt('street', 'Street', { required: true }), + txt('city', 'City'), + txt('state', 'State'), + txt('zip', 'ZIP code'), +]; + +export const phoneParams = [ + { + name: 'phoneNumbers', + type: 'array', + label: 'Phone numbers', + required: true, + help: 'One number per item, for example 4805550123. You are billed per phone number.', + spec: { type: 'text', label: 'Phone number' }, + }, +]; + +export const apiCallParams = [ + txt('url', 'URL', { + required: true, + help: 'Path after /api, without a leading slash, for example v1/property/search. Account balance (wallet) endpoints are not available here.', + }), + { + name: 'method', + type: 'select', + label: 'Method', + required: true, + default: 'GET', + options: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].map(opt), + }, + { + name: 'headers', + type: 'array', + label: 'Headers', + help: 'The authorization header is added for you.', + spec: [txt('key', 'Key'), txt('value', 'Value')], + }, + { + name: 'qs', + type: 'array', + label: 'Query string', + spec: [txt('key', 'Key'), txt('value', 'Value')], + }, + { name: 'body', type: 'text', label: 'Body', help: 'JSON request body, as text. Requests that contain a callback URL field are not sent.' }, +]; diff --git a/test/catalog.test.mjs b/test/catalog.test.mjs new file mode 100644 index 0000000..fc42c80 --- /dev/null +++ b/test/catalog.test.mjs @@ -0,0 +1,262 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { createHash } from 'node:crypto'; +import { render } from './iml.mjs'; + +const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); +const read = (rel) => JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8')); +const catalog = read('scripts/catalog.json'); +const app = read('makecomapp.json'); +const camel = (s) => s.replace(/[-_]+([a-z0-9])/g, (_, c) => c.toUpperCase()); +const files = (key) => app.components.module[key].codeFiles; +const comm = (key) => read(files(key).communication); +const params = (key) => read(files(key).mappableParams); +const request = (key, parameters, extra = {}) => { + const c = comm(key); + const scope = { parameters, connection: { apiKey: 't' }, ...extra }; + return { url: render(c.url, scope), method: render(c.method, scope), qs: render(c.qs, scope), body: render(c.body, scope) }; +}; +const setPath = (obj, p, v) => { + const segs = p.split('.'); + let n = obj; + segs.forEach((s, i) => { + if (i === segs.length - 1) n[s] = v; + else n = n[s] ??= {}; + }); + return obj; +}; +// A fake API response built from the catalog's own result mapping and sample. +const fakeBody = (op) => { + const r = op.result; + const b = {}; + if (r.list) { + setPath(b, r.list, [op.sample]); + for (const [k, p] of Object.entries(r.attach ?? {})) setPath(b, p, { [k]: 1 }); + } else if (r.item) setPath(b, r.item, op.sample); + else if (r.compose) for (const [k, p] of Object.entries(r.compose)) setPath(b, p, op.sample[k]); + return b; +}; + +test('vendored catalog matches its recorded hash and the shared source when present', () => { + const raw = fs.readFileSync(path.join(root, 'scripts/catalog.json'), 'utf8'); + assert.equal(createHash('sha256').update(raw).digest('hex'), fs.readFileSync(path.join(root, 'scripts/catalog.sha256'), 'utf8').trim()); + const shared = path.join(root, '..', 'shared', 'catalog.json'); + if (fs.existsSync(shared)) assert.equal(fs.readFileSync(shared, 'utf8'), raw); +}); + +test('every catalog operation and the polling and instant triggers are present as modules', () => { + for (const op of catalog.ops) assert.ok(app.components.module[camel(op.key)], op.key); + for (const key of ['watchSearchSessions', 'watchSessionProperties', 'watchMonitorEvents', 'watchMonitorEventsWithProperty']) { + assert.ok(app.components.module[key], key); + } + assert.equal(Object.keys(app.components.module).length, 30); +}); + +test('copy lint: one contact, no retired names, no callback fields, no Zapier wording', () => { + const banned = new RegExp(['batch' + 'leads', 'batch\\s*' + 'dialer'].join('|'), 'i'); + const dirs = ['modules', 'webhooks', 'rpcs', 'connections', 'general']; + const all = dirs.flatMap((d) => fs.readdirSync(path.join(root, d), { recursive: true }).map((f) => path.join(root, d, f))).filter((f) => f.endsWith('.json')); + for (const f of [...all, path.join(root, 'makecomapp.json')]) { + const text = fs.readFileSync(f, 'utf8'); + assert.ok(!banned.test(text), f); + assert.ok(!/webhookUrl|errorWebhookUrl/i.test(text), f); + assert.ok(!/\bzap(s|ier)?\b/i.test(text), `${f} mentions Zapier`); + if (f.includes('.samples.')) continue; // sample records contain made-up example data + for (const email of text.match(/[\w.+-]+@[\w-]+\.[\w.]+/g) ?? []) assert.equal(email.replace(/\.$/, ''), 'support@batchservice.com', f); + } +}); + +test('request sweep: every operation builds a versioned URL and a body that renders cleanly', () => { + for (const op of catalog.ops) { + const key = camel(op.key); + const p = {}; + for (const f of op.fields) { + const n = camel(f.key); + if (f.type === 'rows') p[n] = [{ propertyAddress: { street: '1 Main St' }, address: { street: '1 Main St' }, street: '1 Main St' }]; + else if (f.type === 'integer' || f.type === 'number') p[n] = 3; + else if (f.type === 'boolean') p[n] = true; + else if (f.type === 'list') p[n] = ['a']; + else if (f.type === 'choice') p[n] = f.choices[0].value; + else if (f.type === 'json') p[n] = '{"general":{"x":1}}'; + else if (f.type === 'date') p[n] = '2026-01-02T00:00:00.000Z'; + else p[n] = 'abc'; + } + const r = request(key, p); + assert.match(r.url, /^\/v[123]\//, key); + assert.ok(!r.url.includes('{{'), key); + assert.equal(r.method, op.http.method, key); + assert.ok(!JSON.stringify(r.body ?? {}).includes('{{'), `${key} left an expression unresolved`); + } +}); + +test('output sweep: every operation yields the sample fields from a realistic response', () => { + for (const op of catalog.ops) { + const key = camel(op.key); + const c = comm(key); + const body = fakeBody(op); + const iterate = c.response.iterate; + const out = (item) => render(c.response.output, { body, item, parameters: { id: 'x' } }); + const results = iterate + ? render(typeof iterate === 'string' ? iterate : iterate.container, { body }).filter((item) => + typeof iterate === 'string' ? true : render(iterate.condition, { item }), + ).map(out) + : [out()]; + if (op.key === 'reverse_skip_trace') continue; // the sample is the matched row, tested separately + assert.ok(results.length >= 1, key); + for (const k of Object.keys(results[0])) assert.ok(k in read(files(key).samples), `${key}: output field ${k} is not in the sample`); + } +}); + +test('TCPA-flagged phones are explicit on every skip trace style operation', () => { + for (const op of catalog.ops.filter((o) => o.skipTraceLike)) { + const key = camel(op.key); + const base = { phone: '4805550123', properties: [{ propertyAddress: { street: '1 Main St' } }] }; + assert.strictEqual(request(key, base).body.options.includeTCPABlacklistedPhones, false, key); + assert.strictEqual(request(key, { ...base, includeTcpaFlagged: true }).body.options.includeTCPABlacklistedPhones, true, key); + assert.match(app.components.module[key].description, /TCPA/, key); + } + for (const k of ['investorBuybox']) assert.match(app.components.module[k].description, /TCPA/); +}); + +test('batch operations pass the rows straight through and cap them at 100 in the help text', () => { + const rows = [{ propertyAddress: { street: '1 Main St', city: 'Phoenix' }, name: { first: 'Ann' }, requestId: 'r1' }]; + assert.deepEqual(request('batchSkipTrace', { properties: rows }).body.requests, rows); + const spec = params('batchSkipTrace')[0].spec; + assert.deepEqual(spec.map((s) => s.name).sort(), ['name', 'propertyAddress', 'requestId']); + assert.match(params('batchSkipTrace')[0].help, /100/); + assert.deepEqual(request('batchVerifyAddress', { addresses: [{ street: 'a' }] }).body, { requests: [{ street: 'a' }] }); + const lookup = request('batchLookupProperty', { addresses: [{ address: { street: 'a' } }], datasets: ['owner'] }); + assert.deepEqual(lookup.body.options, { datasets: ['owner'] }); +}); + +test('reverse skip trace sends one request and keeps only matched rows', () => { + const r = request('reverseSkipTrace', { email: 'a@example.com' }); + assert.deepEqual(r.body.requests, [{ email: 'a@example.com' }]); + const c = comm('reverseSkipTrace'); + const body = { result: { data: [{ meta: { matched: false } }, { meta: { matched: true }, persons: [{}] }] } }; + const kept = render(c.response.iterate.container, { body }).filter((item) => render(c.response.iterate.condition, { item })); + assert.equal(kept.length, 1); + assert.match(app.components.module.reverseSkipTrace.description, /Fill in at least one of: Phone Number, Email Address/); +}); + +test('permits, geocoding and investors build the documented requests', () => { + const p = request('getPermits', { address: { street: '1 Main St' }, status: { equals: 'active' }, allTags: { inList: ['solar'] }, issueDate: { minDate: '2026-01-02' }, take: 10 }); + assert.equal(p.url, '/v2/property/get-property-permits'); + assert.deepEqual(p.body, { + request: { address: { street: '1 Main St' }, status: { equals: 'active' }, allTags: { inList: ['solar'] }, issueDate: { minDate: '2026-01-02' } }, + options: { take: 10 }, + }); + assert.deepEqual(request('reverseGeocode', { latitude: 33.4, longitude: -112.1 }).body, { request: { latitude: 33.4, longitude: -112.1 }, options: {} }); + assert.deepEqual(request('geocodeAddress', { address: '1 Main St, Phoenix, AZ' }).body, { requests: [{ address: '1 Main St, Phoenix, AZ' }] }); + assert.deepEqual(request('autocompleteAddress', { query: '2800 N 24' }).body, { searchCriteria: { query: '2800 N 24' }, options: { take: 5 } }); + const b = request('investorBuybox', { street: '1 Main St', investor: { classification: { inList: ['flipper'] } }, additionalCriteria: '{"x":{"y":1}}' }); + assert.deepEqual(b.body.searchCriteria, { x: { y: 1 }, compAddress: { street: '1 Main St' }, investor: { classification: { inList: ['flipper'] } } }); + const ip = request('investorProperties', { investorId: 'inv1', includeChildEntities: true }); + assert.deepEqual(ip.body, { searchCriteria: { investorId: 'inv1', includeChildEntities: true }, options: { take: 25 } }); +}); + +test('search sessions: the session ID and cursor pagination are sent, deletes sanitize the ID', () => { + const c = request('createSearchSession', { sessionId: 'my-session', query: 'Tempe, AZ', quickLists: ['vacant'] }); + assert.equal(c.url, '/v1/property/search'); + assert.deepEqual(c.body, { + searchCriteria: { query: 'Tempe, AZ', quickLists: ['vacant'] }, + options: { searchSession: { id: 'my-session' }, take: 1, useCursorPagination: true }, + }); + assert.match(app.components.module.createSearchSession.description, /bill/i); + const l = request('listSearchSessions', {}); + assert.equal(l.url, '/v2/property/search-sessions'); + assert.deepEqual(l.qs, { take: 50 }); + assert.equal(request('deleteSearchSession', { id: 'a/../b.c' }).url, '/v2/property/search-sessions/a____b_c'); + const idParam = params('deleteSearchSession')[0]; + assert.equal(idParam.options, 'rpc://listSearchSessions'); +}); + +test('monitor creation is stream delivery only and never offers a webhook delivery type', () => { + const m = params('createMonitor'); + const type = m.find((p) => p.name === 'deliveryType'); + assert.deepEqual(type.options.map((o) => o.value), ['kinesis', 'event-hub']); + const k = request('createMonitor', { deliveryType: 'kinesis', query: 'Phoenix, AZ', kinesis: { streamName: 's', region: 'us-west-2' }, eventHub: { eventHubName: 'ignored' } }); + assert.deepEqual(k.body, { searchCriteria: { query: 'Phoenix, AZ' }, deliveryConfig: { type: 'kinesis', streamName: 's', region: 'us-west-2' } }); + const e = request('createMonitor', { deliveryType: 'event-hub', eventHub: { eventHubName: 'h' }, kinesis: { streamName: 'ignored' } }); + assert.deepEqual(e.body.deliveryConfig, { type: 'event-hub', eventHub: { eventHubName: 'h' } }); + assert.equal(request('deleteMonitor', { id: 'abc' }).url, '/v2/property-subscription/abc'); + assert.equal(request('getMonitor', { id: 'abc' }).method, 'GET'); + for (const key of ['createMonitor', 'listMonitors', 'getMonitor', 'deleteMonitor']) { + assert.match(app.components.module[key].description, /5 monitors per team/, key); + } +}); + +test('polling triggers: sessions by creation date, session properties by poll time, billing disclosed', () => { + const s = comm('watchSearchSessions'); + assert.deepEqual(render(s.qs, {}), { take: 100 }); + assert.equal(s.response.trigger.date, '{{item.createdAt}}'); + const sample = read(files('watchSearchSessions').samples); + assert.equal(render(s.response.trigger.id, { item: sample }), sample.id); + const sp = comm('watchSessionProperties'); + const req = render(sp.body, { parameters: { sessionId: 'abc', query: 'Mesa, AZ', take: 5 } }); + assert.deepEqual(req, { searchCriteria: { query: 'Mesa, AZ' }, options: { searchSession: { id: 'abc' }, take: 5, useCursorPagination: true } }); + assert.match(app.components.module.watchSessionProperties.description, /Billed per property record delivered/); + assert.match(app.components.module.watchSessionProperties.description, /Max Results|Max results/i); + const names = read(files('watchSessionProperties').staticParams).map((p) => p.name); + assert.ok(names.includes('sessionId') && names.includes('take')); + assert.equal(read(files('watchSessionProperties').staticParams).find((p) => p.name === 'sessionId').required, true); +}); + +test('instant trigger: the hook is the trigger\'s own, criteria come from the webhook parameters', () => { + const w = app.components.webhook.monitorEvents; + const attach = read(w.codeFiles.attach); + const scope = { parameters: { query: 'Phoenix, AZ', quickLists: ['vacant'] }, webhook: { url: 'https://hook.make.com/abc' } }; + assert.deepEqual(render(attach.body, scope), { + searchCriteria: { query: 'Phoenix, AZ', quickLists: ['vacant'] }, + deliveryConfig: { type: 'webhook', url: 'https://hook.make.com/abc' }, + }); + const detach = read(w.codeFiles.detach); + assert.equal(render(detach.url, { webhook: { externalHookId: 'sub1' } }), '/v2/property-subscription/sub1'); + assert.equal(render(attach.response.data.externalHookId, { body: { result: { subscriptionId: 'sub1' } } }), 'sub1'); + const hook = read(w.codeFiles.communication); + const payload = { eventId: 'e1', propertyId: 'p1', addedToSubscriptionIds: ['sub1'] }; + assert.equal(render(hook.condition, { body: payload }), true); + assert.equal(render(hook.condition, { body: { hello: 1 } }), false); + assert.equal(render(hook.output, { body: payload }).id, 'e1'); + assert.equal(render(hook.output, { body: payload }).propertyId, 'p1'); + const params_ = read(w.codeFiles.params).map((p) => p.name); + assert.ok(!params_.some((n) => /include|webhook|url/i.test(n)), params_.join()); +}); + +test('instant trigger: property lookup only happens in the billed variant', () => { + assert.deepEqual(comm('watchMonitorEvents'), {}); + const c = comm('watchMonitorEventsWithProperty'); + const payload = { id: 'e1', eventId: 'e1', propertyId: 'p1' }; + assert.equal(c.url, '/v1/property/lookup/all-attributes'); + assert.deepEqual(render(c.body, { payload }), { requests: [{ propertyId: 'p1' }] }); + const out = render(c.response.output, { payload, body: { results: { properties: [{ _id: 'p1' }] } } }); + assert.deepEqual(out, { ...payload, property: { _id: 'p1' } }); + assert.match(app.components.module.watchMonitorEventsWithProperty.description, /billed per event, per matched property/i); + assert.match(app.components.module.watchMonitorEvents.description, /4 attempts per event/); + assert.match(app.components.module.watchMonitorEvents.description, /Property Record/); +}); + +test('generic API call: wallet, async, path tricks and monitor creation are blocked', () => { + const c = comm('makeAnApiCall'); + const url = (u, method = 'GET') => render(c.url, { parameters: { url: u, method } }); + for (const ok of ['v1/property/search', 'v2/property/search-sessions', 'v3/property/skip-trace']) assert.equal(url(ok, 'POST'), `/${ok}`); + assert.equal(url('v2/property-subscription', 'GET'), '/v2/property-subscription'); + assert.equal(url('v2/property-subscription/abc', 'DELETE'), '/v2/property-subscription/abc'); + for (const bad of ['v1/wallet', 'v2/WALLET/x', 'v3/property/skip-trace/async', 'v1/Async/x', 'v1/../v2/wallet', 'v1/property%2Fwallet', 'v1/x?y=1', 'https://example.com/x', 'x://y']) { + assert.equal(url(bad, 'POST'), '/blocked/not-available', bad); + } + for (const m of ['POST', 'PUT', 'PATCH']) assert.equal(url('v2/property-subscription', m), '/blocked/not-available', m); + for (const body of ['{"web\\u0068ookUrl":"x"}', '{"webhook":"x"}', '{"a":{"errorWebhookUrl":"x"}}', '{"WebhookUrl":"x"}']) { + assert.equal(render(c.url, { parameters: { url: 'v1/property/search', method: 'POST', body } }), '/blocked/not-available', body); + } +}); + +test('blocked API calls send no body or query; the webhook dedupes on eventId', () => { + const c = comm('makeAnApiCall'); + const scope = { parameters: { url: 'v1/wallet', method: 'POST', body: '{"a":1}', qs: [{ key: 'x', value: '1' }] } }; + assert.equal(render(c.body, scope), undefined); + assert.deepEqual(render(c.qs, scope), {}); + assert.equal(JSON.parse(fs.readFileSync(path.join(root, 'webhooks/monitorEvents/monitorEvents.communication.iml.json'), 'utf8')).uid, '{{body.eventId}}'); +}); diff --git a/test/fixtures/address-verify.json b/test/fixtures/address-verify.json new file mode 100644 index 0000000..363d36d --- /dev/null +++ b/test/fixtures/address-verify.json @@ -0,0 +1,61 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "addresses": [ + { + "street": "2800 N 24th St", + "city": "Phoenix", + "state": "AZ", + "zip": "85008", + "county": "Maricopa", + "hash": "90a01b18e3834e349a8d1d07d7e086ba", + "latitude": 33.47865, + "longitude": -112.03029, + "geoLocation": [ + 33.47865, + -112.03029 + ], + "requestId": 0, + "formattedStreet": "N 24th St", + "streetNoUnit": "2800 N 24th St", + "meta": { + "error": false, + "normalized": true, + "hashed": true + }, + "houseNumber": "2800", + "zipPlus4": "1049", + "vacant": "No", + "countyFipsCode": "04013", + "carrierRoute": "C009", + "congressionalDistrict": "07", + "timeZone": "Mountain", + "utcOffset": -7, + "daylightSavingsTime": false, + "dpvMatchCode": "D", + "dpvFootnotes": "AAN1", + "footnotes": "B#H#" + } + ], + "meta": { + "apiVersion": "2.0", + "results": { + "requestCount": 1, + "normalizedCount": 1, + "notNormalizedCount": 0, + "hashedCount": 1, + "notHashedCount": 0, + "errorCount": 0 + }, + "performance": { + "totalRequestTime": 278, + "startTime": "2021-10-06T20:28:23.617Z", + "endTime": "2021-10-06T20:28:23.895Z" + }, + "requestId": "M2CiaQam0XxT64K" + } + } +} \ No newline at end of file diff --git a/test/fixtures/phone-dnc.json b/test/fixtures/phone-dnc.json new file mode 100644 index 0000000..fabaf4b --- /dev/null +++ b/test/fixtures/phone-dnc.json @@ -0,0 +1,49 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "phoneNumbers": [ + { + "number": "6027828692", + "dnc": false, + "meta": { + "error": false, + "matched": true + } + }, + { + "number": "5633574823", + "dnc": true, + "meta": { + "error": false, + "matched": true + } + }, + { + "number": "4808602153", + "dnc": true, + "meta": { + "error": false, + "matched": true + } + } + ], + "meta": { + "apiVersion": "2.0", + "results": { + "requestCount": 3, + "matchCount": 3, + "noMatchCount": 0, + "errorCount": 0 + }, + "performance": { + "totalRequestTime": 433, + "startTime": "2021-10-05T21:18:50.576Z", + "endTime": "2021-10-05T21:18:51.009Z" + }, + "requestId": "qTmKWCL3mDLIpmX" + } + } +} \ No newline at end of file diff --git a/test/fixtures/phone-tcpa.json b/test/fixtures/phone-tcpa.json new file mode 100644 index 0000000..43de91a --- /dev/null +++ b/test/fixtures/phone-tcpa.json @@ -0,0 +1,41 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "phoneNumbers": [ + { + "number": "2024561111", + "tcpa": false, + "meta": { + "error": false, + "matched": false + } + }, + { + "number": "6027828692", + "tcpa": true, + "meta": { + "error": false, + "matched": true + } + } + ], + "meta": { + "apiVersion": "2.15.4", + "results": { + "requestCount": 2, + "matchCount": 1, + "noMatchCount": 1, + "errorCount": 0 + }, + "performance": { + "totalRequestTime": 7, + "startTime": "2022-07-13T09:38:02.738Z", + "endTime": "2022-07-13T09:38:02.745Z" + }, + "requestId": "z5JmWWXdNT18vMV" + } + } +} \ No newline at end of file diff --git a/test/fixtures/phone-verification.json b/test/fixtures/phone-verification.json new file mode 100644 index 0000000..ed9fba8 --- /dev/null +++ b/test/fixtures/phone-verification.json @@ -0,0 +1,40 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "phoneNumbers": [ + { + "carrier": "OMNIPOINT COMMUNICATIONS- INC.", + "dnc": false, + "notFound": false, + "number": "6462467560", + "reachable": true, + "state": "NY", + "tcpa": false, + "tested": true, + "type": "Mobile", + "meta": { + "error": false, + "matched": true + } + } + ], + "meta": { + "apiVersion": "2.0", + "results": { + "requestCount": 1, + "matchCount": 1, + "noMatchCount": 0, + "errorCount": 0 + }, + "performance": { + "totalRequestTime": 6, + "startTime": "2021-10-06T20:45:49.164Z", + "endTime": "2021-10-06T20:45:49.170Z" + }, + "requestId": "OATS6vACiiIxFlT" + } + } +} \ No newline at end of file diff --git a/test/fixtures/property-lookup-all-attributes.json b/test/fixtures/property-lookup-all-attributes.json new file mode 100644 index 0000000..68866c0 --- /dev/null +++ b/test/fixtures/property-lookup-all-attributes.json @@ -0,0 +1,562 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "properties": [ + { + "_id": "76ef71f2bc0c999e87592cd8302c5d32", + "address": { + "houseNumber": "26823", + "street": "26823 N 31st Dr", + "city": "Phoenix", + "cityAliases": [ + "phoenix" + ], + "county": "Maricopa", + "state": "AZ", + "zip": "85083", + "zipPlus4": "5837", + "formattedStreet": "N 31st Dr", + "streetNoUnit": "26823 N 31st Dr", + "localities": [ + "arizona hillcrest" + ], + "latitude": 33.728785, + "longitude": -112.126019, + "countyFipsCode": "04013", + "hash": "fae16e6adfaf0bb0f306ba89bf7b4c1e", + "normalized": true, + "geoStatus": "Rooftop", + "geoStatusCode": "A" + }, + "assessment": { + "assessedImprovementValue": 27730, + "assessedLandValue": 6930, + "totalAssessedValue": 43300, + "assessmentYear": 2024, + "improvementMarketValue": 346400, + "landMarketValue": 86600, + "totalMarketValue": 433000, + "marketValueYear": 2024 + }, + "building": { + "totalBuildingAreaSquareFeet": 1761, + "totalBuildingAreaCode": "B", + "totalBuildingAreaCodeDescription": "Living Area", + "livingAreaSquareFeet": 1761, + "grossBuildingAreaSquareFeet": 2480, + "adjustedBuildingAreaSquareFeet": 2100, + "yearBuilt": 2001, + "effectiveYearBuilt": 2001, + "buildingCount": 1, + "roomCount": 6, + "unitCount": 1, + "calculatedBathroomCount": 2, + "fullBathroomCount": 2, + "bathroomCount": 2, + "bathFixtureCount": 7, + "storyCount": 1, + "garageParkingSpaceCount": 2, + "residentialUnitCount": 1, + "features": [ + "Air Conditioning", + "Refrigeration Air Conditioning", + "Sauna/Steam Room", + "Koi Pond", + "Mobile Home Hookup", + "Patio", + "Garage", + "Pool", + "Elevator" + ], + "airConditioningSourceCode": "J", + "airConditioningSource": "Refrigeration", + "buildingConditionCode": "F", + "buildingCondition": "Average", + "buildingQualityCode": "D", + "buildingQuality": "B", + "constructionTypeCode": "C", + "constructionType": "Frame", + "exteriorWallsCode": "S", + "exteriorWalls": "Stucco", + "garageCode": "I", + "garage": "Garage", + "heatSourceCode": "X", + "heatSource": "Yes", + "patioCode": "A", + "patio": "Patio", + "poolCode": "H", + "pool": "Pool - Yes", + "roofCoverCode": "I", + "roofCover": "Concrete", + "fireplaceCount": 0 + }, + "deedHistory": [ + { + "buyers": [ + "KB HOME SALES PHOENIX INC" + ], + "sellers": [ + "KB HOME PHOENIX INC", + "KAUFMAN & BROAD OF ARIZONA INC" + ], + "recordingDate": "2001-07-30T00:00:00.000Z", + "saleDate": "2001-07-20T00:00:00.000Z", + "documentNumber": "2001-0688814", + "documentTypeCode": "Z", + "documentType": "Corporation Deed", + "salePrice": 0, + "salePriceCode": "Full Consideration", + "transferTax": 412, + "ownerRelationshipType": "Arm's Length", + "multipleOwners": false + }, + { + "buyers": [ + "CHENEY RHONDA" + ], + "sellers": [ + "NUNEZ JR ROBERTO" + ], + "recordingDate": "2002-08-30T00:00:00.000Z", + "saleDate": "2002-08-19T00:00:00.000Z", + "documentNumber": "2002-0890956", + "documentTypeCode": "BF", + "documentType": "Quit Claim Deed", + "salePrice": 0 + }, + { + "buyers": [ + "ANDERSON RHONDA K" + ], + "sellers": [ + "ANDERSON KYLE DOUGLAS" + ], + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleDate": "2005-01-14T00:00:00.000Z", + "documentNumber": "2005-0080877", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + }, + { + "buyers": [ + "ANDERSON RHONDA K" + ], + "sellers": [ + "ANDERSON RHONDA K", + "CHENEY RHONDA" + ], + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleDate": "2005-01-14T00:00:00.000Z", + "documentNumber": "2005-0080876", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + }, + { + "buyers": [ + "ANDERSON KYLE", + "ANDERSON RHONDA K" + ], + "sellers": [], + "recordingDate": "2006-12-29T00:00:00.000Z", + "saleDate": "2006-12-11T00:00:00.000Z", + "documentNumber": "2006-1700576", + "documentTypeCode": "DN", + "documentType": "Stand Alone Mortgage" + }, + { + "buyers": [ + "ANDRESON RHONDA K", + "ANDERSON KYLE" + ], + "sellers": [ + "ANDRESON RHONDA K", + "ANDRESON KYLE" + ], + "recordingDate": "2013-01-18T00:00:00.000Z", + "saleDate": "2012-11-13T00:00:00.000Z", + "documentNumber": "2013-0056003", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true + } + ], + "demographics": { + "age": 52, + "emailDomain": "gmail.com", + "householdSize": 3, + "income": 95000, + "netWorth": 425000, + "discretionaryIncome": 38000, + "homeownerRenterCode": "O", + "homeownerRenter": "Home Owner", + "genderCode": "M", + "gender": "Male", + "maritalStatusCode": "M", + "maritalStatus": "Married", + "childCount": 2, + "hasChildren": true, + "investments": [ + "Real Estate", + "Personal" + ], + "individualEducationCode": "BA", + "individualEducation": "Bachelor's Degree", + "individualOccupationCode": "PRO", + "individualOccupation": "Professional", + "businessOwnerCode": "A", + "businessOwner": "Real Estate", + "petOwner": true, + "millionaire": false, + "recentlyDivorced": false, + "singleParent": false, + "smoker": false, + "religious": true, + "religiousAffiliationCode": "C", + "religiousAffiliation": "Catholic", + "recentlyMoved": false, + "estimatedAreaCreditRating": "B" + }, + "foreclosure": {}, + "permit": { + "permitCount": 3, + "earliestDate": "2015-04-10", + "latestDate": "2022-08-22", + "totalJobValue": 48500, + "allTags": [ + "roofing", + "solar", + "electrical" + ], + "tags": { + "addition": false, + "adu": false, + "bathroom": false, + "battery": false, + "demolition": false, + "electrical": true, + "electricMeter": false, + "evCharger": false, + "fireSprinkler": false, + "gas": false, + "generator": false, + "grading": false, + "heatPump": false, + "hvac": false, + "inspectionPassed": true, + "kitchen": false, + "newConstruction": false, + "plumbing": false, + "poolAndHotTub": false, + "remodel": false, + "roofing": true, + "solar": true, + "waterHeater": false, + "windowDoor": false + } + }, + "general": { + "carrierRoute": "R087", + "vacant": false, + "mailingAddressVacant": false, + "vacantDate": "2021-05-14", + "mobileHome": false, + "standardizedLandUseCode": "R2", + "propertyTypeCategory": "Residential", + "propertyTypeDetail": "Single Family", + "congressionalDistrict": "8", + "timeZone": "Mountain", + "utcOffset": -7, + "daylightSavingsTime": false, + "censusTract": "040136119.001008", + "primaryParcel": true, + "parcelCount": 1 + }, + "ids": { + "addressHash": "fae16e6adfaf0bb0f306ba89bf7b4c1e", + "apn": "205-03-224", + "oldApn": "205032242", + "fipsCode": "04013" + }, + "legal": { + "assessorsMapReference": "MB 51824", + "cityTownshipMunicipality": "Phoenix", + "legalDescription": "ARIZONA HILLCREST MCR 518-24", + "lotCode": "S", + "lotNumber": "199", + "sectionTownshipRangeMeridian": "SEC 35 TWN 5N RNG 2E", + "subdivisionName": "ARIZONA HILLCREST", + "tractNumber": "ARIZONA HILLCREST" + }, + "lot": { + "lotSizeAcres": 0.14637281910009184, + "lotSizeSquareFeet": 6376, + "zoningCode": "R-8" + }, + "mortgageHistory": [ + { + "borrowers": [ + "CHENEY RHONDA" + ], + "saleDate": "2002-08-19T00:00:00.000Z", + "recordingDate": "2002-08-30T00:00:00.000Z", + "dueDate": "2032-09-01T00:00:00.000Z", + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": "6.43" + }, + { + "borrowers": [ + "ANDERSON RHONDA K" + ], + "saleDate": "2005-01-14T00:00:00.000Z", + "recordingDate": "2005-01-20T00:00:00.000Z", + "dueDate": "2035-02-01T00:00:00.000Z", + "lenderName": "JPMORGAN CHASE BANK NA", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "AH", + "loanType": "Unknown (DEFAULT)", + "loanAmount": 156800, + "loanTermMonths": 360, + "interestRate": "5.67" + }, + { + "borrowers": [ + "ANDERSON RHONDA K" + ], + "saleDate": "2005-01-14T00:00:00.000Z", + "recordingDate": "2005-01-20T00:00:00.000Z", + "dueDate": "2035-01-28T00:00:00.000Z", + "lenderName": "JPMORGAN CHASE BANK NA", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 19000, + "loanTermMonths": 360, + "interestRate": "5.67" + }, + { + "borrowers": [ + "ANDERSON KYLE", + "ANDERSON RHONDA K" + ], + "saleDate": "2006-12-11T00:00:00.000Z", + "recordingDate": "2006-12-29T00:00:00.000Z", + "dueDate": "2026-12-19T00:00:00.000Z", + "lenderName": "USAA FSB", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 75299, + "loanTermMonths": 240, + "interestRate": "6.33" + }, + { + "borrowers": [ + "ANDRESON RHONDA K", + "ANDERSON KYLE" + ], + "saleDate": "2012-11-13T00:00:00.000Z", + "recordingDate": "2013-01-18T00:00:00.000Z", + "dueDate": "2027-12-01T00:00:00.000Z", + "lenderName": "USAA FSB", + "lenderType": "Bank", + "lenderTypeCode": "B", + "loanTypeCode": "U", + "loanType": "New Conventional", + "loanAmount": 112000, + "loanTermMonths": 180, + "interestRate": "3.31" + } + ], + "openLien": { + "allLoanTypes": [], + "juniorLoanTypes": [], + "totalOpenLienCount": 1, + "mortgages": [ + { + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": 6.43, + "loanType": "New Conventional", + "subordinateLoanFlag": false, + "equityFlag": false, + "helocFlag": false, + "pudRider": false, + "condoRider": false, + "multiFamilyRider": false, + "assumable": false, + "privateLender": false, + "secondHomeRider": false, + "sellerCarryBack": false, + "balloonRider": false + } + ] + }, + "owner": { + "fullName": "Kyle Anderson; Rhonda K Anderson", + "mailingAddress": { + "houseNumber": "26823", + "street": "26823 N 31st Dr", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85083", + "zipPlus4": "5837", + "formattedStreet": "N 31st Dr", + "streetNoUnit": "26823 N 31st Dr", + "hash": "fae16e6adfaf0bb0f306ba89bf7b4c1e" + }, + "names": [ + { + "first": "Kyle", + "last": "Anderson", + "full": "Kyle Anderson" + }, + { + "first": "Rhonda", + "middle": "K", + "last": "Anderson", + "full": "Rhonda K Anderson" + } + ], + "ownerOccupied": true, + "mailingOptOut": false, + "hasLandline": true, + "ownerStatusTypeCode": "B", + "ownerStatusType": "Individual", + "ownershipRightsCode": "AT", + "ownershipRights": "Joint Tenants" + }, + "propertyOwnerProfile": { + "averageYearBuilt": 2001, + "propertiesCount": 1, + "propertiesTotalEquity": 449821, + "propertiesTotalEstimatedValue": 449821 + }, + "quickLists": { + "absenteeOwner": false, + "absenteeOwnerInState": false, + "absenteeOwnerOutOfState": false, + "activeListing": false, + "activeAuction": false, + "cashBuyer": false, + "corporateOwned": false, + "expiredListing": false, + "freeAndClear": true, + "highEquity": true, + "inherited": false, + "listedBelowMarketPrice": false, + "lowEquity": false, + "mailingAddressVacant": false, + "onMarket": false, + "outOfStateOwner": false, + "ownerOccupied": true, + "pendingListing": false, + "preforeclosure": false, + "recentlySold": false, + "samePropertyAndMailingAddress": true, + "taxDefault": false, + "tiredLandlord": false, + "unknownEquity": false, + "vacant": false, + "hasHoa": true, + "hasHoaFees": true, + "canceledListing": false, + "noticeOfSale": false, + "noticeOfDefault": false, + "noticeOfLisPendens": false + }, + "sale": { + "lastTransfer": { + "documentNumber": "2013-0056003", + "documentType": "Intra-Family Transfer", + "priceCodeDescription": "Transfer Tax on document indicated as EXEMPT.", + "priceCode": "Z", + "recordingDate": "2013-01-18T00:00:00.000Z", + "saleDate": "2012-11-13T00:00:00.000Z" + }, + "lastSale": { + "saleBuyers": [ + "Michael R Torres" + ], + "saleSellers": [ + "BlueSky Holdings LLC" + ], + "salePriceIsEstimated": false + }, + "flipLength": 0, + "flipLengthCategory": 0, + "flipProfit": 0, + "priorTransfer": { + "documentNumber": "2005-0080877", + "documentType": "Intra-Family Transfer", + "priceCodeDescription": "Non-arms length transaction.", + "priceCode": "N", + "salePriceIsEstimated": false + }, + "priorSale": { + "recordingDate": "2005-01-20T00:00:00.000Z", + "saleBuyers": [ + "BlueSky Holdings LLC" + ], + "saleSellers": [ + "Estate Of Dorothy J Miller" + ], + "salePriceIsEstimated": false + } + }, + "tax": { + "taxAmount": 19, + "taxYear": 2023, + "taxDelinquentYear": 2022, + "taxRateCodeArea": "971300" + }, + "valuation": { + "estimatedValue": 449821, + "priceRangeMin": 382348, + "priceRangeMax": 517294, + "standardDeviation": 12, + "confidenceScore": 88, + "asOfDate": "2024-02-28T00:00:00.000Z", + "equityCurrentEstimatedBalance": 449821, + "ltv": 0, + "equityPercent": 100 + }, + "meta": { + "propertyDateModified": "2024-04-11T09:54:59.574Z" + } + } + ], + "meta": { + "apiVersion": "6.0.0", + "performance": { + "totalRequestTime": 167, + "startTime": "2024-04-16T18:07:52.824Z", + "endTime": "2024-04-16T18:07:52.991Z" + }, + "results": { + "requestCount": 1, + "matchCount": 1, + "noMatchCount": 0, + "errorCount": 0 + }, + "requestId": "EBilj51hcf9RPC8" + } + } +} \ No newline at end of file diff --git a/test/fixtures/property-search.json b/test/fixtures/property-search.json new file mode 100644 index 0000000..4579b43 --- /dev/null +++ b/test/fixtures/property-search.json @@ -0,0 +1,593 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "properties": [ + { + "_id": "5c2b4e5fdca9199c5e2a041c2e8166e1", + "address": { + "addressValidity": "Valid", + "houseNumber": "4522", + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "county": "Maricopa", + "state": "AZ", + "zip": "85086", + "zipPlus4": "1431", + "latitude": 33.867445, + "longitude": -112.157639, + "countyFipsCode": "04013", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + }, + "deedHistory": [ + { + "buyers": [ + "TOUSA HOMES INC", + "ENGLE HOMES" + ], + "sellers": [ + "ACACIA CREDIT FUND 8-A LLC" + ], + "recordingDate": "2005-03-10T00:00:00.000Z", + "saleDate": "2005-03-04T00:00:00.000Z", + "documentNumber": "2005.294894", + "documentTypeCode": "S", + "documentType": "Cash Sale Deed", + "salePrice": 252492, + "salePriceCode": "Full Consideration", + "transferTax": 412, + "ownerRelationshipType": "Arm's Length", + "multipleOwners": false, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "3150 S 48th St Ste 100", + "city": "Phoenix", + "state": "AZ", + "zip": "85040", + "hash": "9612f33c41b36a5e163072df7d8535c1" + } + }, + { + "buyers": [ + "BARNES DAVID", + "BARNES TRISHA N" + ], + "sellers": [ + "ENGLE/SUNBELT LLC" + ], + "recordingDate": "2005-11-17T00:00:00.000Z", + "saleDate": "2005-11-14T00:00:00.000Z", + "documentNumber": "2005-1744001", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 422475, + "newConstruction": true, + "transactionType": "New construction residential transactions", + "transactionTypeCode": "C", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "BARNES DAVID" + ], + "sellers": [ + "BARNES DAVID", + "BARNES TRISHA N" + ], + "recordingDate": "2006-02-21T00:00:00.000Z", + "saleDate": "2006-02-13T00:00:00.000Z", + "documentNumber": "2006-0236246", + "documentTypeCode": "AQ", + "documentType": "Intra-Family Transfer", + "salePrice": 0, + "interFamily": true, + "transactionType": "Non-arms length transactions with valid title company name (no accomodations)", + "transactionTypeCode": "A", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "ROMERO MARIA ESTELA", + "ROMERO OCTAVIO R" + ], + "sellers": [ + "BARNES DAVID" + ], + "recordingDate": "2010-10-27T00:00:00.000Z", + "saleDate": "2010-09-03T00:00:00.000Z", + "documentNumber": "2010-0941098", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 154000, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "THE FIVE CLUB TRUST" + ], + "sellers": [ + "ROMERO OCTAVIO RIMOLDI", + "ROMERO MARIA ESTELA" + ], + "recordingDate": "2015-10-23T00:00:00.000Z", + "saleDate": "2015-09-09T00:00:00.000Z", + "documentNumber": "2015-0764207", + "documentTypeCode": "Q", + "documentType": "Beneficiary Deed (Buyer ID = BE)", + "salePrice": 0, + "transactionType": "Non-arms length transactions with valid title company name (no accomodations)", + "transactionTypeCode": "A" + }, + { + "buyers": [ + "MADISON NATHAN S", + "MADISON MARY L" + ], + "sellers": [ + "ROMERO OCTAVIO RIMOLDI", + "ROMERO MARIA ESTELA" + ], + "recordingDate": "2020-08-11T00:00:00.000Z", + "saleDate": "2020-08-06T00:00:00.000Z", + "documentNumber": "2020.732181", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 389900, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "4522 W Ravina Ln", + "city": "Phoenix", + "state": "AZ", + "zip": "85086", + "hash": "94d976d3a42b939e67c4015ee1a95f44" + } + }, + { + "buyers": [ + "YUET P AND I-JUNG K POON FAMILY TRUST" + ], + "sellers": [ + "MADISON NATHAN S", + "MADISON MARY L" + ], + "recordingDate": "2022-04-11T00:00:00.000Z", + "saleDate": "2022-03-31T00:00:00.000Z", + "documentNumber": "2022.316997", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 570000, + "transactionType": "Arms-length residential transactions (purchase/resales)", + "transactionTypeCode": "B", + "mailingAddress": { + "street": "19675 La Mar Dr", + "city": "Cupertino", + "state": "CA", + "zip": "95014", + "hash": "f3b3f6919555dd565a3a8eeee61b15b4" + } + }, + { + "buyers": [ + "4522 W RAVINA LLC" + ], + "sellers": [ + "YUET P AND I-JUNG K POON FAMILY TRUST" + ], + "recordingDate": "2023-02-09T00:00:00.000Z", + "saleDate": "2023-02-09T00:00:00.000Z", + "documentNumber": "2023.67061", + "documentTypeCode": "BZ", + "documentType": "Warranty Deed", + "salePrice": 0, + "transactionType": "Non-arms length transactions with valid title company name (no accomodations)", + "transactionTypeCode": "A", + "mailingAddress": { + "street": "3225 McLeod Dr # 777", + "city": "Las Vegas", + "state": "NV", + "zip": "89121", + "hash": "45ba83fc378f6c2ee5a655f19dd58860" + } + } + ], + "demographics": { + "age": 60, + "emailDomain": "gmail.com", + "householdSize": 1, + "income": 170000, + "netWorth": 437000, + "discretionaryIncome": 64750, + "homeownerRenterCode": "O", + "homeownerRenter": "Home Owner", + "genderCode": "F", + "gender": "Female", + "maritalStatusCode": "S", + "maritalStatus": "Single", + "religious": true, + "religiousAffiliationCode": "P", + "religiousAffiliation": "Protestant", + "individualEducationCode": "E", + "individualEducation": "Attended College", + "individualOccupation": "Professional / Technical", + "individualOccupationCode": "1", + "estimatedAreaCreditRating": "A" + }, + "foreclosure": { + "statusCode": "E", + "status": "Rescission Recording", + "recordingDate": "2010-11-12T00:00:00.000Z", + "filingDate": "2010-11-12T00:00:00.000Z", + "documentNumber": "2010-0991603", + "documentTypeCode": "L", + "documentType": "Notice of Rescission", + "releaseDate": "2010-11-12T00:00:00.000Z" + }, + "ids": { + "apn": "203-04-206" + }, + "intel": { + "salePropensity": 12.8, + "salePropensityStatus": "Available", + "salePropensityCategory": "Medium" + }, + "involuntaryLien": { + "liens": [ + { + "documentNumber": "225074972", + "documentType": "Financing Statement", + "documentTypeCode": "ED", + "lienType": "Economic Lien", + "lienTypeCode": "B", + "recordingDate": "2025-07-02T00:00:00.000Z" + } + ] + }, + "listing": { + "status": "Off Market", + "price": 2700, + "rental": false, + "originalListingDate": "2025-04-29T00:00:00.000Z", + "maxListPrice": 570000, + "maxListPriceDate": "2022-04-11T00:00:00.000Z", + "minListPrice": 2450, + "minListPriceDate": "2020-06-04T00:00:00.000Z", + "soldPrice": 570000, + "soldDate": "2022-04-11T00:00:00.000Z", + "failedListingDate": "2025-05-01T00:00:00.000Z", + "bedroomCount": 5, + "bathroomCount": 3, + "totalBuildingAreaSquareFeet": 2906, + "lotSizeSquareFeet": 6969, + "propertyType": "Single Family", + "yearBuilt": 2005, + "appliances": [ + "Dishwasher", + "Dryer", + "Garbage disposal", + "Microwave", + "Refrigerator", + "Washer" + ], + "roofTypes": [ + "Tile" + ], + "coolingTypes": [ + "Other" + ], + "heatingTypes": [ + "Forced air", + "Gas" + ], + "newConstruction": false, + "listingUrl": "https://www.zillow.com/homedetails/4522-W-Ravina-Ln-Anthem-AZ-85086/67774980_zpid/", + "livingArea": 2906, + "taxes": [ + { + "amount": 2263.56, + "year": 2024 + }, + { + "amount": 2732.26, + "year": 2023 + }, + { + "amount": 2684.5, + "year": 2022 + }, + { + "amount": 2708, + "year": 2021 + }, + { + "amount": 2629.9, + "year": 2020 + }, + { + "amount": 2547.14, + "year": 2019 + }, + { + "amount": 2547.14, + "year": 2018 + }, + { + "amount": 2464.6, + "year": 2017 + }, + { + "amount": 2095.58, + "year": 2016 + }, + { + "amount": 2095.58, + "year": 2015 + }, + { + "year": 2014 + }, + { + "year": 2013 + }, + { + "year": 2012 + }, + { + "year": 2008 + }, + { + "year": 2007 + }, + { + "year": 2006 + }, + { + "year": 2005 + } + ], + "salePriceIsEstimated": false + }, + "mortgageHistory": [ + { + "borrowers": [ + "BARNES DAVID" + ], + "saleDate": "2006-02-10T00:00:00.000Z", + "recordingDate": "2006-02-21T00:00:00.000Z", + "dueDate": "2021-02-15T00:00:00.000Z", + "lenderName": "GREENPOINT MORTGAGE FUNDING INC", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanTypeCode": "K", + "loanType": "Credit Line (Revolving)", + "loanAmount": 42000, + "loanTermMonths": 180, + "interestRate": "6.15", + "transactionType": "HELOCS - both purchase money and non-purchase money HELOCS", + "transactionTypeCode": "F", + "documentDate": "2006-02-10T00:00:00.000Z" + }, + { + "borrowers": [ + "MADISON NATHAN S", + "MADISON MARY L" + ], + "saleDate": "2021-10-04T00:00:00.000Z", + "recordingDate": "2021-10-08T00:00:00.000Z", + "dueDate": "2051-11-01T00:00:00.000Z", + "lenderName": "ON Q FINANCIAL INC", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanTypeCode": "U", + "loanType": "New Conventional", + "loanAmount": 345000, + "loanTermMonths": 360, + "interestRate": "2.8", + "transactionType": "Refi loans and 2nd trust deeds (purchase and non-purchase money trust deeds)", + "transactionTypeCode": "G", + "documentDate": "2021-10-04T00:00:00.000Z" + } + ], + "openLien": { + "totalOpenLienCount": 1, + "mortgages": [ + { + "lenderName": "CAPITOL COMMERCE MTG CO", + "lenderType": "Mortgage Company", + "loanAmount": 160500, + "loanTermMonths": 360, + "interestRate": 6.43, + "loanType": "New Conventional", + "subordinateLoanFlag": false, + "equityFlag": false, + "helocFlag": false, + "pudRider": false, + "condoRider": false, + "multiFamilyRider": false, + "assumable": false, + "privateLender": false, + "secondHomeRider": false, + "sellerCarryBack": false, + "balloonRider": false + } + ] + }, + "owner": { + "fullName": "4522 W RAVINA LLC", + "mailingAddress": { + "addressValidity": "Partially Valid", + "street": "3225 McLeod Dr # 777", + "city": "Las Vegas", + "county": "Clark", + "state": "NV", + "zip": "89121", + "zipPlus4": "2257", + "hash": "45ba83fc378f6c2ee5a655f19dd58860", + "deliveryPointCode": "256", + "dpvMatchCode": "S", + "dpvFootnotes": "AABBCC" + }, + "names": [ + { + "last": "4522 W RAVINA LLC", + "full": "4522 W RAVINA LLC" + } + ], + "mailingOptOut": false, + "hasLandline": true + }, + "permit": { + "permitCount": 2, + "earliestDate": "2018-06-14", + "latestDate": "2021-09-03", + "totalJobValue": 22750, + "allTags": [ + "hvac", + "electrical" + ], + "tags": { + "addition": false, + "adu": false, + "bathroom": false, + "battery": false, + "demolition": false, + "electrical": true, + "electricMeter": false, + "evCharger": false, + "fireSprinkler": false, + "gas": false, + "generator": false, + "grading": false, + "heatPump": false, + "hvac": true, + "inspectionPassed": true, + "kitchen": false, + "newConstruction": false, + "plumbing": false, + "poolAndHotTub": false, + "remodel": false, + "roofing": false, + "solar": false, + "waterHeater": false, + "windowDoor": false + } + }, + "propertyOwnerProfile": { + "averageYearBuilt": 1983, + "propertiesCount": 8614, + "mortgagesTotalBalance": 1647737268, + "mortgagesCount": 4325, + "mortgagesAverageBalance": 380980 + }, + "quickLists": { + "absenteeOwner": true, + "absenteeOwnerInState": false, + "absenteeOwnerOutOfState": true, + "activeListing": false, + "activeAuction": false, + "cashBuyer": true, + "corporateOwned": true, + "expiredListing": false, + "failedListing": true, + "freeAndClear": true, + "fixAndFlip": false, + "forSaleByOwner": false, + "highEquity": true, + "inherited": false, + "involuntaryLien": false, + "listedBelowMarketPrice": true, + "lowEquity": false, + "mailingAddressVacant": false, + "onMarket": false, + "outOfStateOwner": true, + "ownerOccupied": false, + "pendingListing": false, + "preforeclosure": false, + "recentlySold": false, + "samePropertyAndMailingAddress": false, + "seniorOwner": false, + "taxDefault": false, + "tiredLandlord": false, + "unknownEquity": false, + "vacant": false, + "vacantLot": false, + "hasHoa": true, + "hasHoaFees": true, + "canceledListing": false, + "noticeOfSale": false, + "noticeOfDefault": false, + "noticeOfLisPendens": false + }, + "sale": { + "priorSale": { + "mortgages": [ + { + "documentNumber": "20200732182", + "pageNumber": null, + "bookNumber": null, + "recordingDate": "2020-08-11T00:00:00.000Z", + "loanAmount": 311920, + "loanTypeCode": "U", + "loanType": "New Conventional", + "financingTypeCode": null, + "financingType": null, + "interestRate": 3.1, + "dueDate": "2050-09-01T00:00:00.000Z", + "lenderName": "ON Q FINANCIAL INC", + "lenderType": "Mortgage Company", + "lenderTypeCode": "M", + "loanTerm": "360" + } + ] + } + }, + "valuation": { + "estimatedValue": 567493, + "priceRangeMin": 482369, + "priceRangeMax": 652617, + "standardDeviation": 6, + "confidenceScore": 94, + "asOfDate": "2025-07-09T00:00:00.000Z", + "equityCurrentEstimatedBalance": 567493, + "ltv": 0, + "equityPercent": 100 + } + } + ], + "meta": { + "apiVersion": "6.65.1", + "performance": { + "totalRequestTime": 24, + "startTime": "2025-10-29T16:46:34.557Z", + "endTime": "2025-10-29T16:46:34.581Z" + }, + "results": { + "resultCount": 1, + "resultsFound": 435641 + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/property-skip-trace.json b/test/fixtures/property-skip-trace.json new file mode 100644 index 0000000..de47cc4 --- /dev/null +++ b/test/fixtures/property-skip-trace.json @@ -0,0 +1,122 @@ +{ + "status": { + "code": 200, + "text": "OK" + }, + "results": { + "persons": [ + { + "bankruptcy": {}, + "dnc": {}, + "emails": [ + { + "email": "johndoe@gmail.net" + } + ], + "mailingAddress": {}, + "name": { + "first": "john", + "last": "doe" + }, + "phoneNumbers": [ + { + "number": "1111111111", + "carrier": "OMNIPOINT COMMUNICATIONS- INC.", + "type": "Mobile", + "tested": true, + "reachable": true, + "score": 100 + }, + { + "number": "2222222222", + "carrier": "VERIZON NEW YORK- INC.", + "type": "Land Line", + "tested": false, + "reachable": false, + "score": 95 + }, + { + "number": "3333333333", + "carrier": "VERIZON NEW YORK- INC.", + "type": "Land Line", + "tested": true, + "reachable": true, + "score": 90 + }, + { + "number": "4444444444", + "carrier": "VERIZON NEW YORK- INC.", + "type": "Land Line", + "tested": true, + "reachable": true, + "score": 85 + } + ], + "litigator": false, + "propertyAddress": { + "houseNumber": "1011", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507", + "formattedStreet": "Rosegold St", + "streetNoUnit": "1011 Rosegold St", + "hash": "b7bd4cea51b26af459febfbf822c99e2" + }, + "property": { + "address": { + "houseNumber": "1011", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507", + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "formattedStreet": "Rosegold St", + "streetNoUnit": "1011 Rosegold St" + }, + "owner": { + "name": { + "first": "john", + "last": "doe" + }, + "mailingAddress": { + "houseNumber": "1011", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "formattedStreet": "Rosegold St", + "streetNoUnit": "1011 Rosegold St", + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "zipPlus4": "2507" + } + } + }, + "meta": { + "matched": true, + "error": false + } + } + ], + "meta": { + "apiVersion": "2.28.0", + "performance": { + "totalRequestTime": 1460, + "startTime": "2022-12-30T11:58:24.469Z", + "endTime": "2022-12-30T11:58:25.930Z" + }, + "results": { + "requestCount": 1, + "matchCount": 1, + "noMatchCount": 0, + "errorCount": 0 + }, + "requestId": "NbL5WXZMuBOCW1s" + } + } +} \ No newline at end of file diff --git a/test/fixtures/v3-property-skip-trace.json b/test/fixtures/v3-property-skip-trace.json new file mode 100644 index 0000000..bdd55f9 --- /dev/null +++ b/test/fixtures/v3-property-skip-trace.json @@ -0,0 +1,193 @@ +{ + "status": { + "code": 200, + "text": "OK", + "message": "Success" + }, + "result": { + "data": [ + { + "input": { + "propertyAddress": { + "street": "1011 Rosegold St", + "city": "Franklin Square", + "state": "NY", + "zip": "11010" + } + }, + "property": { + "id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "address": { + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "addressValidity": "Valid", + "fullAddress": "1011 Rosegold St, Franklin Square, NY 11010-2507", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507" + }, + "mailingAddress": { + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "addressValidity": "Valid", + "fullAddress": "1011 Rosegold St, Franklin Square, NY 11010-2507", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507" + }, + "owners": [ + { + "name": { + "first": "John", + "last": "Doe", + "full": "John Doe", + "akas": [ + { + "first": "John", + "middle": "Quinn", + "last": "Doe", + "full": "John Quinn Doe", + "rank": 2 + }, + { + "first": "Jhon", + "last": "Doe", + "full": "Jhon Doe", + "rank": 3 + } + ] + } + } + ] + }, + "persons": [ + { + "propertyOwner": true, + "name": { + "first": "John", + "last": "Doe", + "full": "John Doe", + "akas": [ + { + "first": "John", + "middle": "Quinn", + "last": "Doe", + "full": "John Quinn Doe", + "rank": 2 + }, + { + "first": "Jhon", + "last": "Doe", + "full": "Jhon Doe", + "rank": 3 + } + ] + }, + "addresses": [ + { + "hash": "b7bd4cea51b26af459febfbf822c99e2", + "addressValidity": "Valid", + "fullAddress": "1011 Rosegold St, Franklin Square, NY 11010-2507", + "street": "1011 Rosegold St", + "city": "Franklin Square", + "county": "Nassau", + "state": "NY", + "zip": "11010", + "zipPlus4": "2507", + "rank": 1, + "propertyMailingAddress": true + }, + { + "hash": "c8ce5dfa62c37bg560gfcgcg933da0f3", + "addressValidity": "Valid", + "fullAddress": "500 Maple Ave, Hempstead, NY 11550-3210", + "street": "500 Maple Ave", + "city": "Hempstead", + "county": "Nassau", + "state": "NY", + "zip": "11550", + "zipPlus4": "3210", + "rank": 2, + "propertyMailingAddress": false + } + ], + "phones": [ + { + "rank": 1, + "number": "1112223333", + "type": "Mobile", + "carrier": "OMNIPOINT COMMUNICATIONS- INC.", + "tested": true, + "reachable": true, + "dnc": false, + "tcpa": false + }, + { + "rank": 2, + "number": "4445556666", + "type": "Land Line", + "carrier": "VERIZON NEW YORK- INC.", + "tested": false, + "reachable": false, + "dnc": false, + "tcpa": false + }, + { + "rank": 3, + "number": "7778889999", + "type": "Land Line", + "carrier": "VERIZON NEW YORK- INC.", + "tested": true, + "reachable": true, + "dnc": true, + "tcpa": false + }, + { + "rank": 4, + "number": "1011121314", + "type": "Land Line", + "carrier": "VERIZON NEW YORK- INC.", + "tested": true, + "reachable": true, + "dnc": false, + "tcpa": true + } + ], + "emails": [ + { + "rank": 1, + "email": "johndoe@gmail.net", + "tested": true + } + ], + "litigator": false, + "deceased": false, + "dob": "1985-07-21T00:00:00.000Z" + } + ], + "meta": { + "matched": true, + "error": false + } + } + ], + "options": { + "includeTCPABlacklistedPhones": true, + "dateFormat": "iso-date-time" + }, + "meta": { + "apiVersion": "3.1.0", + "results": { + "requestCount": 1, + "matchCount": 1, + "noMatchCount": 0, + "errorCount": 0 + }, + "requestId": "01KESNCCY9751A25KYQYV54ASC" + } + } +} \ No newline at end of file diff --git a/test/iml.mjs b/test/iml.mjs new file mode 100644 index 0000000..d977040 --- /dev/null +++ b/test/iml.mjs @@ -0,0 +1,51 @@ +// A small evaluator for the subset of IML (Make's expression language) used by this app. +// It is NOT Make's real engine. It exists so the request bodies can be checked offline. +const isEmpty = (v) => v === undefined || v === null || v === ''; +const fns = { + ifempty: (a, b) => (isEmpty(a) ? b : a), + __if: (c, a, b) => (c ? a : b), + omit: (o, ...keys) => Object.fromEntries(Object.entries(o || {}).filter(([k]) => !keys.includes(k))), + length: (v) => (v === undefined || v === null ? 0 : v.length), + first: (a) => (Array.isArray(a) ? a[0] : undefined), + lower: (s) => String(s ?? '').toLowerCase(), + trim: (s) => String(s ?? '').trim(), + contains: (s, x) => (s ?? '').includes(x), + replace: (s, a, b) => String(s ?? '').split(a).join(b), + formatDate: (d) => (d ? new Date(d).toISOString().slice(0, 10) : undefined), + parseJSON: (s) => (s === undefined || s === null || s === '' ? undefined : JSON.parse(s)), + toCollection: (arr, k, v) => Object.fromEntries((arr || []).map((e) => [e[k], e[v]])), +}; + +const evalExpr = (expr, scope) => { + // IML reads missing paths as empty instead of failing, so use optional chaining. + const js = expr.replace(/\bif\(/g, '__if(').replace(/([\w)\]])\.(?=[A-Za-z_])/g, '$1?.'); + const f = new Function('scope', 'fns', `with (fns) { with (scope) { return (${js}); } }`); + return f(scope, fns); +}; + +const whole = /^\{\{([\s\S]*)\}\}$/; + +export const render = (node, scope) => { + if (typeof node === 'string') { + const m = node.match(whole); + if (m && !m[1].includes('{{')) return m[1].trim() === '...' ? node : evalExpr(m[1], scope); + return node.replace(/\{\{([\s\S]*?)\}\}/g, (_, e) => { + const v = evalExpr(e, scope); + return v === undefined || v === null ? '' : String(v); + }); + } + if (Array.isArray(node)) return node.map((n) => render(n, scope)).filter((v) => v !== undefined); + if (node && typeof node === 'object') { + const out = {}; + for (const [k, v] of Object.entries(node)) { + if (k === '{{...}}') { + Object.assign(out, render(v, scope)); + continue; + } + const r = render(v, scope); + if (r !== undefined) out[k] = r; + } + return out; + } + return node; +}; diff --git a/test/replay.test.mjs b/test/replay.test.mjs new file mode 100644 index 0000000..094d995 --- /dev/null +++ b/test/replay.test.mjs @@ -0,0 +1,182 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { render } from './iml.mjs'; + +const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); +const read = (rel) => JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8')); +const comm = (name) => read(`modules/${name}/${name}.communication.iml.json`); +const fixture = (name) => read(`test/fixtures/${name}.json`); +const request = (name, parameters) => { + const c = comm(name); + const scope = { parameters, connection: { apiKey: 'test-token' } }; + return { url: render(c.url, scope), method: render(c.method, scope), body: render(c.body, scope) }; +}; +const respond = (name, body, extra = {}) => { + const out = render(comm(name).response.output ?? '{{item}}', { body, ...extra }); + return out; +}; +const iterate = (name, body) => render(comm(name).response.iterate, { body }); + +test('search: only filled filters are sent, take defaults to 25', () => { + const r = request('searchProperties', { + query: 'Phoenix, AZ', + quickLists: ['absentee-owner', 'not-vacant'], + building: { bedroomCount: { min: 3 } }, + owner: { ownerOccupied: { equals: false } }, + }); + assert.equal(r.url, '/v1/property/search'); + assert.deepEqual(r.body, { + searchCriteria: { + query: 'Phoenix, AZ', + quickLists: ['absentee-owner', 'not-vacant'], + building: { bedroomCount: { min: 3 } }, + owner: { ownerOccupied: { equals: false } }, + }, + options: { take: 25 }, + }); +}); + +test('search: take and skip are passed and kept out of the criteria', () => { + const r = request('searchProperties', { query: 'Tempe', take: 0, skip: 50 }); + assert.deepEqual(r.body, { searchCriteria: { query: 'Tempe' }, options: { take: 0, skip: 50 } }); +}); + +test('search: output is each property plus the result counts', () => { + const f = fixture('property-search'); + const items = iterate('searchProperties', f); + assert.equal(items.length, f.results.properties.length); + const out = render(comm('searchProperties').response.output, { item: items[0], body: f }); + assert.equal(out._id, items[0]._id); + assert.deepEqual(out.searchMeta, f.results.meta.results); +}); + +test('lookup: sends exactly one identifier', () => { + const byAddress = request('lookupProperty', { + identifyBy: 'address', + address: { street: '1 Main St', city: 'Phoenix', state: 'AZ', zip: '85001' }, + propertyId: 'IGNORED', + hash: 'IGNORED', + }); + assert.deepEqual(byAddress.body.requests, [{ address: { street: '1 Main St', city: 'Phoenix', state: 'AZ', zip: '85001' } }]); + + const byApn = request('lookupProperty', { identifyBy: 'apn', apn: '123-45', countyFipsCode: '04013', propertyId: 'IGNORED' }); + assert.deepEqual(byApn.body.requests, [{ apn: '123-45', countyFipsCode: '04013' }]); + + const byId = request('lookupProperty', { identifyBy: 'propertyId', propertyId: 'abc' }); + assert.deepEqual(byId.body.requests, [{ propertyId: 'abc' }]); +}); + +test('lookup: datasets and skip trace only when chosen', () => { + const r = request('lookupProperty', { + identifyBy: 'hash', + hash: 'h1', + datasets: ['owner', 'mortgage-liens'], + includeSkipTrace: true, + }); + assert.deepEqual(r.body.options, { skipTrace: true, datasets: ['owner', 'mortgage-liens'] }); + const none = request('lookupProperty', { identifyBy: 'hash', hash: 'h1' }); + assert.deepEqual(none.body.options, {}); +}); + +test('lookup: output is the first property', () => { + const f = fixture('property-lookup-all-attributes'); + assert.equal(respond('lookupProperty', f)._id, f.results.properties[0]._id); +}); + +for (const [name, version] of [['skipTraceV1', 'v1'], ['skipTraceV3', 'v3']]) { + test(`skip trace ${version}: path is versioned and TCPA-flagged phones are excluded by default`, () => { + const r = request(name, { + findBy: 'address', + propertyAddress: { street: '1 Main St', city: 'Phoenix', state: 'AZ', zip: '85001' }, + apn: 'IGNORED', + }); + assert.equal(r.url, `/${version}/property/skip-trace`); + assert.deepEqual(r.body.requests, [ + { propertyAddress: { street: '1 Main St', city: 'Phoenix', state: 'AZ', zip: '85001' } }, + ]); + assert.deepEqual(r.body.options, { includeTCPABlacklistedPhones: false }); + }); + + test(`skip trace ${version}: the TCPA option is explicit when turned on`, () => { + const r = request(name, { + findBy: 'apn', + apn: '1-2', + countyFipsCode: '04013', + includeTcpaFlaggedPhones: true, + prioritizeMobilePhones: true, + name: { first: 'Ann', last: 'Lee' }, + }); + assert.deepEqual(r.body.requests, [{ apn: '1-2', countyFipsCode: '04013', name: { first: 'Ann', last: 'Lee' } }]); + assert.deepEqual(r.body.options, { includeTCPABlacklistedPhones: true, prioritizeMobilePhones: true }); + }); +} + +test('skip trace: V1 yields a bundle per person, V3 a bundle per property', () => { + assert.equal(iterate('skipTraceV1', fixture('property-skip-trace')).length, fixture('property-skip-trace').results.persons.length); + const v3 = fixture('v3-property-skip-trace'); + const items = iterate('skipTraceV3', v3); + assert.equal(items.length, v3.result.data.length); + assert.ok(Array.isArray(items[0].persons)); +}); + +test('address verify: street required, empty parts dropped, output is first address', () => { + const r = request('verifyAddress', { street: '1 Main St', zip: '85001' }); + assert.deepEqual(r.body, { requests: [{ street: '1 Main St', zip: '85001' }] }); + const f = fixture('address-verify'); + assert.equal(respond('verifyAddress', f).hash, f.results.addresses[0].hash); +}); + +for (const [name, path_, file] of [ + ['verifyPhones', '/v1/phone/verification', 'phone-verification'], + ['checkDnc', '/v1/phone/dnc', 'phone-dnc'], + ['checkTcpa', '/v1/phone/tcpa', 'phone-tcpa'], +]) { + test(`${name}: sends the numbers as a flat array and yields one bundle per number`, () => { + const r = request(name, { phoneNumbers: ['4805550123', '6025550199'] }); + assert.equal(r.url, path_); + assert.deepEqual(r.body, { requests: ['4805550123', '6025550199'] }); + const f = fixture(file); + assert.equal(iterate(name, f).length, f.results.phoneNumbers.length); + }); +} + +test('api call: builds url, method, headers and query; wallet paths are redirected', () => { + const r = request('makeAnApiCall', { url: 'v1/property/search', method: 'POST', body: '{"a":1}' }); + assert.deepEqual(r.body, { a: 1 }); + assert.equal(r.url, '/v1/property/search'); + assert.equal(r.method, 'POST'); + const c = comm('makeAnApiCall'); + const scope = { + parameters: { url: 'v1/x', method: 'GET', headers: [{ key: 'X-A', value: '1' }], qs: [{ key: 'take', value: '1' }] }, + }; + assert.deepEqual(render(c.headers, scope), { 'X-A': '1' }); + assert.deepEqual(render(c.qs, scope), { take: '1' }); + for (const bad of ['v1/Wallet/balance', 'v2/wallet', ' v1/WALLET ']) { + assert.equal(request('makeAnApiCall', { url: bad, method: 'GET' }).url, '/blocked/not-available'); + } +}); + +test('connection check treats 400 and 2xx as valid and 401/403 as invalid', () => { + const c = read('connections/batchdata/batchdata.communication.iml.json'); + assert.deepEqual(c.body, { requests: [] }); + const valid = (statusCode) => render(c.response.valid, { statusCode }); + assert.equal(valid(200), true); + assert.equal(valid(400), true); + assert.equal(valid(401), false); + assert.equal(valid(403), false); + assert.equal(valid(500), false); + assert.equal(render(c.headers.Authorization, { parameters: { apiKey: 't' } }), 'Bearer t'); + assert.equal(render(c.response.data, { parameters: { apiKey: 't' } }).apiKey, 't'); +}); + +test('base: bearer header comes from the connection and errors read the BatchData body', () => { + const b = read('general/base.iml.json'); + assert.equal(render(b.headers.Authorization, { connection: { apiKey: 'k' } }), 'Bearer k'); + const msg = render(b.response.error.message, { statusCode: 422, body: { status: { message: 'Bad input' } } }); + assert.equal(msg, '[422] Bad input'); + const fallback = render(b.response.error.message, { statusCode: 500, body: {} }); + assert.match(fallback, /^\[500\]/); + assert.equal(b.response.error['401'].type, 'InvalidAccessTokenError'); +}); diff --git a/test/structure.test.mjs b/test/structure.test.mjs new file mode 100644 index 0000000..ad0cd8a --- /dev/null +++ b/test/structure.test.mjs @@ -0,0 +1,148 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; + +const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); +const read = (rel) => JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8')); +const app = read('makecomapp.json'); +const walk = (dir) => + fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => { + const p = path.join(dir, e.name); + if (['node_modules', '.git', '.secrets'].includes(e.name)) return []; + return e.isDirectory() ? walk(p) : [p]; + }); +const allFiles = walk(root); +const jsonFiles = allFiles.filter((f) => f.endsWith('.json')); + +test('every JSON file parses', () => { + for (const f of jsonFiles) JSON.parse(fs.readFileSync(f, 'utf8')); + assert.ok(jsonFiles.length > 30); +}); + +test('manifest points at files that exist', () => { + for (const rel of Object.values(app.generalCodeFiles)) assert.ok(fs.existsSync(path.join(root, rel)), rel); + for (const c of Object.values(app.components.connection)) { + for (const rel of Object.values(c.codeFiles)) assert.ok(fs.existsSync(path.join(root, rel)), rel); + } + for (const [key, m] of Object.entries(app.components.module)) { + assert.ok(['search', 'action', 'universal', 'trigger', 'instant_trigger'].includes(m.moduleType), key); + assert.equal(m.connection, 'batchdata'); + assert.ok(m.label && m.description, key); + for (const rel of Object.values(m.codeFiles)) assert.ok(fs.existsSync(path.join(root, rel)), rel); + } +}); + +test('id mapping covers every module and groups cover every module', () => { + const keys = Object.keys(app.components.module).sort(); + const mapped = app.origins[0].idMapping.module.map((m) => m.local).sort(); + assert.deepEqual(mapped, keys); + const grouped = read('modules/groups.json').flatMap((g) => g.modules).sort(); + assert.deepEqual(grouped, keys); +}); + +test('triggers: polling ones declare a trigger rule, instant ones point at a declared webhook', () => { + const polling = Object.entries(app.components.module).filter(([, m]) => m.moduleType === 'trigger'); + const instant = Object.entries(app.components.module).filter(([, m]) => m.moduleType === 'instant_trigger'); + assert.equal(polling.length, 2); + assert.equal(instant.length, 2); + for (const [key, m] of polling) { + const rule = read(m.codeFiles.communication).response.trigger; + assert.ok(['id', 'date'].includes(rule.type), key); + assert.ok(['asc', 'desc', 'unordered'].includes(rule.order), key); + assert.ok(m.codeFiles.staticParams, key); + assert.equal(m.codeFiles.mappableParams, undefined, key); + } + for (const [key, m] of instant) { + assert.ok(app.components.webhook[m.webhook], key); + assert.equal(m.actionCrud, undefined, key); + } +}); + +test('the webhook component creates and removes its own monitor and nothing else', () => { + const w = app.components.webhook.monitorEvents; + assert.equal(w.webhookType, 'web'); + assert.equal(w.connection, 'batchdata'); + for (const rel of Object.values(w.codeFiles)) assert.ok(fs.existsSync(path.join(root, rel)), rel); + const attach = read(w.codeFiles.attach); + assert.equal(attach.method, 'POST'); + assert.equal(attach.url, '/v2/property-subscription'); + assert.deepEqual(attach.body.deliveryConfig, { type: 'webhook', url: '{{webhook.url}}' }); + assert.equal(attach.response.data.externalHookId, '{{body.result.subscriptionId}}'); + const detach = read(w.codeFiles.detach); + assert.equal(detach.method, 'DELETE'); + assert.equal(detach.url, '/v2/property-subscription/{{webhook.externalHookId}}'); + assert.deepEqual(app.origins[0].idMapping.webhook.map((x) => x.local), ['monitorEvents']); +}); + +test('RPC dropdowns are declared, mapped and used', () => { + assert.deepEqual(Object.keys(app.components.rpc).sort(), ['listMonitors', 'listSearchSessions']); + assert.deepEqual(app.origins[0].idMapping.rpc.map((x) => x.local).sort(), ['listMonitors', 'listSearchSessions']); + for (const r of Object.values(app.components.rpc)) for (const rel of Object.values(r.codeFiles)) assert.ok(fs.existsSync(path.join(root, rel)), rel); + const used = JSON.stringify(read(app.components.module.deleteSearchSession.codeFiles.mappableParams)); + assert.match(used, /rpc:\/\/listSearchSessions/); +}); + +test('module URLs use a versioned API path and never the wallet', () => { + for (const [key, m] of Object.entries(app.components.module)) { + const comm = read(m.codeFiles.communication); + if (m.moduleType !== 'universal' && comm.url) assert.match(comm.url, /^\/v[123]\//, key); + } +}); + +test('no callback fields and no wallet endpoints anywhere in the app', () => { + const skip = ['test', 'scripts', 'DEVELOPMENT.md']; + for (const f of allFiles) { + const rel = path.relative(root, f); + if (skip.some((s) => rel.startsWith(s))) continue; + if (rel.endsWith('.png') || rel === 'package-lock.json') continue; + const text = fs.readFileSync(f, 'utf8'); + assert.ok(!/webhookUrl|errorWebhookUrl/i.test(text), `${rel} mentions a callback field`); + if (rel.startsWith('modules/') || rel.startsWith('connections/') || rel.startsWith('general/')) { + const wallet = text.match(/wallet/gi) || []; + const allowed = rel.includes('makeAnApiCall'); + assert.ok(wallet.length === 0 || allowed, `${rel} mentions wallet`); + } + } +}); + +test('retired product names never appear', () => { + for (const f of allFiles) { + if (f.includes(`${path.sep}test${path.sep}`)) continue; + const text = fs.readFileSync(f, 'utf8'); + assert.ok(!/batchleads|batch dialer|batchdialer/i.test(text), f); + } +}); + +test('billing is disclosed on every data module', () => { + for (const [key, m] of Object.entries(app.components.module)) { + if (m.moduleType === 'universal') continue; + assert.match(m.description, /bill/i, key); + } +}); + +test('phone and skip trace modules carry the compliance note', () => { + for (const key of ['skipTraceV1', 'skipTraceV3', 'verifyPhones', 'checkDnc', 'checkTcpa']) { + assert.match(app.components.module[key].description, /TCPA/, key); + } +}); + +test('the connection hides the token in logs and has no secret literal', () => { + const comm = read('connections/batchdata/batchdata.communication.iml.json'); + assert.deepEqual(comm.log.sanitize, ['request.headers.authorization']); + assert.deepEqual(read('general/base.iml.json').log.sanitize, ['request.headers.authorization']); + const params = read('connections/batchdata/batchdata.params.iml.json'); + assert.equal(params[0].type, 'password'); +}); + +test('secrets are git-ignored', () => { + assert.match(fs.readFileSync(path.join(root, '.gitignore'), 'utf8'), /\.secrets/); +}); + +test('every module has samples that match its interface top-level names', () => { + for (const [key, m] of Object.entries(app.components.module)) { + const iface = read(m.codeFiles.interface).map((f) => f.name).sort(); + const sample = Object.keys(read(m.codeFiles.samples)).sort(); + assert.deepEqual(iface, sample, key); + } +}); diff --git a/webhooks/monitorEvents/monitorEvents.attach.iml.json b/webhooks/monitorEvents/monitorEvents.attach.iml.json new file mode 100644 index 0000000..e4ad6dc --- /dev/null +++ b/webhooks/monitorEvents/monitorEvents.attach.iml.json @@ -0,0 +1,21 @@ +{ + "url": "/v2/property-subscription", + "method": "POST", + "body": { + "searchCriteria": { + "{{...}}": "{{if(parameters.additionalCriteria, parseJSON(parameters.additionalCriteria), undefined)}}", + "query": "{{ifempty(parameters.query, undefined)}}", + "quickLists": "{{if(length(parameters.quickLists) > 0, parameters.quickLists, undefined)}}", + "orQuickLists": "{{if(length(parameters.orQuickLists) > 0, parameters.orQuickLists, undefined)}}" + }, + "deliveryConfig": { + "type": "webhook", + "url": "{{webhook.url}}" + } + }, + "response": { + "data": { + "externalHookId": "{{body.result.subscriptionId}}" + } + } +} diff --git a/webhooks/monitorEvents/monitorEvents.communication.iml.json b/webhooks/monitorEvents/monitorEvents.communication.iml.json new file mode 100644 index 0000000..55a8e56 --- /dev/null +++ b/webhooks/monitorEvents/monitorEvents.communication.iml.json @@ -0,0 +1,8 @@ +{ + "condition": "{{length(ifempty(body.eventId, '')) > 0}}", + "uid": "{{body.eventId}}", + "output": { + "{{...}}": "{{body}}", + "id": "{{body.eventId}}" + } +} diff --git a/webhooks/monitorEvents/monitorEvents.detach.iml.json b/webhooks/monitorEvents/monitorEvents.detach.iml.json new file mode 100644 index 0000000..63f2b43 --- /dev/null +++ b/webhooks/monitorEvents/monitorEvents.detach.iml.json @@ -0,0 +1,4 @@ +{ + "url": "/v2/property-subscription/{{webhook.externalHookId}}", + "method": "DELETE" +} diff --git a/webhooks/monitorEvents/monitorEvents.params.iml.json b/webhooks/monitorEvents/monitorEvents.params.iml.json new file mode 100644 index 0000000..9dab103 --- /dev/null +++ b/webhooks/monitorEvents/monitorEvents.params.iml.json @@ -0,0 +1,340 @@ +[ + { + "name": "query", + "label": "Location Query", + "help": "City, county, ZIP code or address text, for example \"Phoenix, AZ\".", + "type": "text" + }, + { + "name": "quickLists", + "label": "Quick Lists (Include)", + "help": "Properties must match ALL selected lists.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "orQuickLists", + "label": "Quick Lists (Match Any)", + "help": "Properties must match AT LEAST ONE selected list.", + "type": "array", + "spec": { + "type": "select", + "options": [ + { + "label": "absentee-owner", + "value": "absentee-owner" + }, + { + "label": "active-auction", + "value": "active-auction" + }, + { + "label": "active-listing", + "value": "active-listing" + }, + { + "label": "canceled-listing", + "value": "canceled-listing" + }, + { + "label": "cash-buyer", + "value": "cash-buyer" + }, + { + "label": "corporate-owned", + "value": "corporate-owned" + }, + { + "label": "expired-listing", + "value": "expired-listing" + }, + { + "label": "failed-listing", + "value": "failed-listing" + }, + { + "label": "fix-and-flip", + "value": "fix-and-flip" + }, + { + "label": "free-and-clear", + "value": "free-and-clear" + }, + { + "label": "for-sale-by-owner", + "value": "for-sale-by-owner" + }, + { + "label": "has-hoa", + "value": "has-hoa" + }, + { + "label": "has-hoa-fees", + "value": "has-hoa-fees" + }, + { + "label": "high-equity", + "value": "high-equity" + }, + { + "label": "inherited", + "value": "inherited" + }, + { + "label": "involuntary-lien", + "value": "involuntary-lien" + }, + { + "label": "in-state-absentee-owner", + "value": "in-state-absentee-owner" + }, + { + "label": "listed-below-market-price", + "value": "listed-below-market-price" + }, + { + "label": "low-equity", + "value": "low-equity" + }, + { + "label": "mailing-address-vacant", + "value": "mailing-address-vacant" + }, + { + "label": "notice-of-default", + "value": "notice-of-default" + }, + { + "label": "notice-of-lis-pendens", + "value": "notice-of-lis-pendens" + }, + { + "label": "notice-of-sale", + "value": "notice-of-sale" + }, + { + "label": "on-market", + "value": "on-market" + }, + { + "label": "out-of-state-absentee-owner", + "value": "out-of-state-absentee-owner" + }, + { + "label": "out-of-state-owner", + "value": "out-of-state-owner" + }, + { + "label": "owner-occupied", + "value": "owner-occupied" + }, + { + "label": "pending-listing", + "value": "pending-listing" + }, + { + "label": "preforeclosure", + "value": "preforeclosure" + }, + { + "label": "recently-sold", + "value": "recently-sold" + }, + { + "label": "same-property-and-mailing-address", + "value": "same-property-and-mailing-address" + }, + { + "label": "senior-owner", + "value": "senior-owner" + }, + { + "label": "tax-default", + "value": "tax-default" + }, + { + "label": "tired-landlord", + "value": "tired-landlord" + }, + { + "label": "trust-owned", + "value": "trust-owned" + }, + { + "label": "unknown-equity", + "value": "unknown-equity" + }, + { + "label": "vacant", + "value": "vacant" + }, + { + "label": "vacant-lot", + "value": "vacant-lot" + } + ] + } + }, + { + "name": "additionalCriteria", + "label": "Additional Criteria (JSON)", + "help": "Optional JSON object merged into the search criteria. The fields above win on conflict. Callback URL keys are ignored. Optional JSON object with extra filters, for example {\"general\":{\"propertyTypeCategory\":{\"equals\":\"Single Family\"}}}. Fields you fill in above take priority.", + "type": "text" + } +]