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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ A monitor reports when a property is added to it, removed from it or changed.

Search Properties, Get Investor Properties, Find Investors (Buy Box), Get Property Permits, List Search Sessions and Watch New Search Sessions have a **Limit** field. It is the total number of records you want. Make fetches them in pages of up to 100 and never asks for more than your Limit, so the Limit is also the most you can be billed for in one run. Search Properties and List Search Sessions also have **Skip** to start part-way through the results.

Each record comes out as its own bundle.
Each record comes out as its own bundle. On billed searches (Search Properties, Get Investor Properties, Find Investors (Buy Box) and Get Property Permits) the default Limit is 1, so a test run does not bill a full page. Raise it when you need more records. Watch New Properties in a Search Session (Billed) defaults to 5 properties per check.

## Billing

Expand Down
4 changes: 4 additions & 0 deletions connections/batchdata/batchdata.communication.iml.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
"data": {
"apiKey": "{{parameters.apiKey}}"
},
"metadata": {
"type": "text",
"value": "{{if(length(parameters.apiKey) >= 8, 'Token ending ' + substring(parameters.apiKey, length(parameters.apiKey) - 4), if(length(parameters.apiKey) > 0, 'API token', 'API token missing'))}}"
},
"error": {
"401": {
"type": "InvalidAccessTokenError",
Expand Down
8 changes: 8 additions & 0 deletions general/base.iml.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@
"type": "InvalidAccessTokenError",
"message": "Your BatchData API token is invalid or expired. Create a new connection with a valid token."
},
"402": {
"type": "DataError",
"message": "{{ifempty(body.status.message, 'Your BatchData account balance is too low for this request.')}} Add funds in your BatchData account, then retry. Contact support@batchservice.com for help."
},
"403": {
"type": "RuntimeError",
"message": "Your BatchData API token does not have access to this data. Contact support@batchservice.com to enable it."
},
"404": {
"type": "RuntimeError",
"message": "[404] {{ifempty(body.status.message, 'Not found. Check the ID or path.')}}"
},
"429": {
"type": "RateLimitError",
"message": "The BatchData rate limit was reached. Wait a moment and try again."
Expand Down
8 changes: 4 additions & 4 deletions modules/getPermits/getPermits.communication.iml.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"countyFipsCode": "{{ifempty(parameters.countyFipsCode, undefined)}}"
},
"options": {
"take": "{{min(100, ifempty(parameters.limit, 25))}}"
"take": "{{min(100, ifempty(parameters.limit, 1))}}"
}
},
"response": {
Expand All @@ -25,7 +25,7 @@
"property": "{{body.result.property}}",
"searchMeta": "{{body.result.meta.results}}"
},
"limit": "{{ifempty(parameters.limit, 25)}}"
"limit": "{{ifempty(parameters.limit, 1)}}"
},
"pagination": {
"body": {
Expand All @@ -42,10 +42,10 @@
"countyFipsCode": "{{ifempty(parameters.countyFipsCode, undefined)}}"
},
"options": {
"take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}",
"take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}",
"skip": "{{0 + (pagination.page - 1) * 100}}"
}
},
"condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}"
"condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}"
}
}
4 changes: 2 additions & 2 deletions modules/getPermits/getPermits.mappable-params.iml.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,12 @@
"name": "limit",
"type": "uinteger",
"label": "Limit",
"default": 25,
"default": 1,
"required": true,
"validate": {
"min": 1,
"max": 5000
},
"help": "The maximum number of permits to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested. Each page is billed per property, so a Limit above 100 sends more than one billed request."
"help": "The maximum number of permits to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. Later modules run once per permit. Each page is billed per property, so a Limit above 100 sends more than one billed request."
}
]
8 changes: 4 additions & 4 deletions modules/investorBuybox/investorBuybox.communication.iml.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
}
},
"options": {
"take": "{{min(100, ifempty(parameters.limit, 25))}}"
"take": "{{min(100, ifempty(parameters.limit, 1))}}"
}
},
"response": {
Expand All @@ -24,7 +24,7 @@
"searchMeta": "{{body.result.meta.results}}",
"warnings": "{{body.result.warnings}}"
},
"limit": "{{ifempty(parameters.limit, 25)}}"
"limit": "{{ifempty(parameters.limit, 1)}}"
},
"pagination": {
"body": {
Expand All @@ -40,10 +40,10 @@
}
},
"options": {
"take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}",
"take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}",
"skip": "{{0 + (pagination.page - 1) * 100}}"
}
},
"condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}"
"condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@
"name": "limit",
"type": "uinteger",
"label": "Limit",
"default": 25,
"default": 1,
"required": true,
"validate": {
"min": 1,
"max": 5000
},
"help": "The maximum number of investors to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested."
"help": "The maximum number of investors to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. Later modules run once per investor. You are billed per record returned."
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"includeChildEntities": "{{if(parameters.includeChildEntities == true, true, undefined)}}"
},
"options": {
"take": "{{min(100, ifempty(parameters.limit, 25))}}",
"take": "{{min(100, ifempty(parameters.limit, 1))}}",
"pageCursor": "{{ifempty(parameters.pageCursor, undefined)}}"
}
},
Expand All @@ -19,7 +19,7 @@
"resultsFound": "{{body.result.resultsFound}}",
"nextPageCursor": "{{body.result.nextPageCursor}}"
},
"limit": "{{ifempty(parameters.limit, 25)}}"
"limit": "{{ifempty(parameters.limit, 1)}}"
},
"pagination": {
"body": {
Expand All @@ -29,10 +29,10 @@
"includeChildEntities": "{{if(parameters.includeChildEntities == true, true, undefined)}}"
},
"options": {
"take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}",
"take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}",
"pageCursor": "{{body.result.nextPageCursor}}"
}
},
"condition": "{{length(ifempty(body.result.nextPageCursor, '')) > 0 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}"
"condition": "{{length(ifempty(body.result.nextPageCursor, '')) > 0 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
"name": "limit",
"type": "uinteger",
"label": "Limit",
"default": 25,
"default": 1,
"required": true,
"validate": {
"min": 1,
"max": 5000
},
"help": "The maximum number of properties to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested."
"help": "The maximum number of properties to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. Later modules run once per property. You are billed per record returned."
},
{
"name": "pageCursor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"searchCriteria": "{{omit(parameters, 'limit', 'skip')}}",
"options": {
"skip": "{{ifempty(parameters.skip, undefined)}}",
"take": "{{min(100, ifempty(parameters.limit, 25))}}"
"take": "{{min(100, ifempty(parameters.limit, 1))}}"
}
},
"response": {
Expand All @@ -14,16 +14,16 @@
"{{...}}": "{{item}}",
"searchMeta": "{{body.results.meta.results}}"
},
"limit": "{{ifempty(parameters.limit, 25)}}"
"limit": "{{ifempty(parameters.limit, 1)}}"
},
"pagination": {
"body": {
"searchCriteria": "{{omit(parameters, 'limit', 'skip')}}",
"options": {
"skip": "{{ifempty(parameters.skip, 0) + (pagination.page - 1) * 100}}",
"take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}"
"take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}"
}
},
"condition": "{{body.results.meta.results.resultsFound > ifempty(parameters.skip, 0) + (pagination.page - 1) * 100 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}"
"condition": "{{body.results.meta.results.resultsFound > ifempty(parameters.skip, 0) + (pagination.page - 1) * 100 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -701,13 +701,13 @@
"name": "limit",
"type": "uinteger",
"label": "Limit",
"default": 25,
"default": 1,
"required": true,
"validate": {
"min": 1,
"max": 5000
},
"help": "The maximum number of properties to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested. You are billed per record returned."
"help": "The maximum number of properties to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. You are billed per record returned. Later modules run once per property, so keep Limit at 1 unless you need more."
},
{
"name": "skip",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,9 @@
{
"name": "take",
"label": "Max Results",
"help": "Whole number from 1 to 100. Default 25. You are billed per property delivered on each check.",
"help": "Whole number from 1 to 100. Default 5. You are billed per property delivered on each check. Use a low number while you test.",
"type": "integer",
"default": 25,
"default": 5,
"validate": {
"min": 1,
"max": 100
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "batchdata-make",
"version": "0.3.0",
"version": "0.3.1",
"private": true,
"description": "BatchData custom app for Make",
"type": "module",
Expand Down
16 changes: 10 additions & 6 deletions scripts/catalog-modules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ const trig = (key) => catalog.triggers.find((t) => t.key === key);
staticParams: staticFromFields(t),
sampleFrom: () => ({ ...t.sample, searchMeta: { totalCount: 1 } }),
};
const take = catalogModules.watchSessionProperties.staticParams.find((x) => x.name === 'take');
if (!take) throw new Error('watchSessionProperties has no take parameter');
take.default = 5;
take.help = 'Whole number from 1 to 100. Default 5. You are billed per property delivered on each check. Use a low number while you test.';
}

// ---------- instant trigger: webhook component + two modules ----------
Expand Down Expand Up @@ -401,27 +405,27 @@ const swapTakeForLimit = (m, param) => {
};
{
const m = catalogModules.getPermits;
swapTakeForLimit(m, limitParam(25, { what: 'permits', extra: ' Each page is billed per property, so a Limit above 100 sends more than one billed request.' }));
offsetPaging(m.communication, { def: 25, found: 'body.result.meta.results.resultsFound', where: 'body' });
swapTakeForLimit(m, limitParam(1, { what: 'permits', extra: ' Later modules run once per permit. Each page is billed per property, so a Limit above 100 sends more than one billed request.' }));
offsetPaging(m.communication, { def: 1, found: 'body.result.meta.results.resultsFound', where: 'body' });
}
{
const m = catalogModules.investorBuybox;
swapTakeForLimit(m, limitParam(25, { what: 'investors' }));
offsetPaging(m.communication, { def: 25, found: 'body.result.meta.results.resultsFound', where: 'body' });
swapTakeForLimit(m, limitParam(1, { what: 'investors', extra: ' Later modules run once per investor. You are billed per record returned.' }));
offsetPaging(m.communication, { def: 1, found: 'body.result.meta.results.resultsFound', where: 'body' });
}
{
// Cursor paging only (skip is rejected). A full last page still returns a cursor, and the request
// that follows it is empty and not billed.
const m = catalogModules.investorProperties;
swapTakeForLimit(m, limitParam(25, { what: 'properties' }));
swapTakeForLimit(m, limitParam(1, { what: 'properties', extra: ' Later modules run once per property. You are billed per record returned.' }));
m.description = m.description
.replace('Use the "nextPageCursor" value of the last result as Page Cursor to read the next page.', 'Set a Limit and Make reads the pages for you; each result also carries the "nextPageCursor" of its page.')
.replace('(take sets the maximum cost of a call)', '(the Limit sets the maximum cost of a run)');
if (/Page Cursor to read|take sets/.test(m.description)) throw new Error('investorProperties description still has the old paging text');
const cur = m.params.find((x) => x.name === 'pageCursor');
cur.label = 'Start from page cursor';
cur.help = 'Optional. Leave empty to start at the first page. The Limit already pages for you, so use this only to continue an earlier run from its Next Page Cursor.';
cursorPaging(m.communication, { def: 25, cursor: 'body.result.nextPageCursor' });
cursorPaging(m.communication, { def: 1, cursor: 'body.result.nextPageCursor' });
}
{
const m = catalogModules.listSearchSessions;
Expand Down
9 changes: 8 additions & 1 deletion scripts/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ const modules = {
},
};

offsetPaging(modules.searchProperties.communication, { def: 25, found: 'body.results.meta.results.resultsFound', where: 'body', skip: 'skip' });
offsetPaging(modules.searchProperties.communication, { def: 1, found: 'body.results.meta.results.resultsFound', where: 'body', skip: 'skip' });
Object.assign(modules, catalogModules);

const baseGroups = [
Expand Down Expand Up @@ -293,7 +293,9 @@ write('general/base.iml.json', {
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.' },
402: { type: 'DataError', message: "{{ifempty(body.status.message, 'Your BatchData account balance is too low for this request.')}} Add funds in your BatchData account, then retry. Contact support@batchservice.com for help." },
403: { type: 'RuntimeError', message: 'Your BatchData API token does not have access to this data. Contact support@batchservice.com to enable it.' },
404: { type: 'RuntimeError', message: "[404] {{ifempty(body.status.message, 'Not found. Check the ID or path.')}}" },
429: { type: 'RateLimitError', message: 'The BatchData rate limit was reached. Wait a moment and try again.' },
},
},
Expand All @@ -317,6 +319,11 @@ write('connections/batchdata/batchdata.communication.iml.json', {
response: {
valid: '{{statusCode == 400 || (statusCode >= 200 && statusCode < 300)}}',
data: { apiKey: '{{parameters.apiKey}}' },
// Make shows the app name beside every connection, so the label never repeats "BatchData".
metadata: {
type: 'text',
value: "{{if(length(parameters.apiKey) >= 8, 'Token ending ' + substring(parameters.apiKey, length(parameters.apiKey) - 4), if(length(parameters.apiKey) > 0, 'API token', 'API token missing'))}}",
},
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.' },
Expand Down
2 changes: 1 addition & 1 deletion scripts/params.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const searchParams = [
},
],
},
limitParam(25, { what: 'properties', extra: ' You are billed per record returned.' }),
limitParam(1, { what: 'properties', extra: ' You are billed per record returned. Later modules run once per property, so keep Limit at 1 unless you need more.' }),
{ name: 'skip', type: 'uinteger', label: 'Skip', help: 'Number of records to skip before the first one returned. Leave empty to start at the first record.' },
];

Expand Down
2 changes: 1 addition & 1 deletion test/catalog.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ test('permits, geocoding and investors build the documented requests', () => {
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 } });
assert.deepEqual(ip.body, { searchCriteria: { investorId: 'inv1', includeChildEntities: true }, options: { take: 1 } });
});

test('search sessions: the session ID and cursor pagination are sent, deletes sanitize the ID', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/pagination.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ for (const name of PAGED) {

test(`${name}: the default Limit is a single request`, () => {
const r = run(name, { ...params }, 1000);
const def = name.includes('Sessions') ? (name === 'watchSearchSessions' ? 100 : 50) : 25;
const def = { searchProperties: 1, investorProperties: 1, investorBuybox: 1, getPermits: 1, listSearchSessions: 50, watchSearchSessions: 100 }[name];
assert.equal(r.items.length, Math.min(def, 1000));
assert.equal(r.requests.length, 1);
});
Expand Down
4 changes: 2 additions & 2 deletions test/replay.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const respond = (name, body, extra = {}) => {
};
const iterate = (name, body) => render(comm(name).response.iterate, { body });

test('search: only filled filters are sent, the Limit defaults to 25', () => {
test('search: only filled filters are sent, the Limit defaults to 1', () => {
const r = request('searchProperties', {
query: 'Phoenix, AZ',
quickLists: ['absentee-owner', 'not-vacant'],
Expand All @@ -34,7 +34,7 @@ test('search: only filled filters are sent, the Limit defaults to 25', () => {
building: { bedroomCount: { min: 3 } },
owner: { ownerOccupied: { equals: false } },
},
options: { take: 25 },
options: { take: 1 },
});
});

Expand Down
Loading
Loading