scrapercity 1.0.18 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_DOCS.txt +16 -0
- package/SKILL.md +3 -2
- package/bin/cli.mjs +26 -2
- package/bin/mcp.mjs +16 -2
- package/lib/catalog.generated.mjs +4 -3
- package/lib/client.mjs +4 -0
- package/package.json +1 -1
package/AGENT_DOCS.txt
CHANGED
|
@@ -193,6 +193,10 @@ GET /api/v1/database/leads?title=CTO&country=United States&hasEmail=true&page=1&
|
|
|
193
193
|
Company filters: keywords (company tags, whole-word, any match), revenueMin, revenueMax (USD),
|
|
194
194
|
companyCountry, companyState, companyCity (company HQ, not the person)
|
|
195
195
|
Exclusions: notTitle, notKeywords, notIndustry
|
|
196
|
+
Website technologies (repeat the parameter for several): technologies (runs any of),
|
|
197
|
+
technologiesAll (runs all of), notTechnologies (runs none of). E.g. companies on
|
|
198
|
+
Klaviyo, PayPal and Shopify without Facebook Pixel:
|
|
199
|
+
technologiesAll=Klaviyo&technologiesAll=PayPal&technologiesAll=Shopify¬Technologies=Facebook%20Pixel
|
|
196
200
|
url=<people-search URL>: use that search's filters (other params override).
|
|
197
201
|
The response adds search_translation: { applied, approximated, not_supported }.
|
|
198
202
|
A URL with no filters the database can use returns 400.
|
|
@@ -251,6 +255,18 @@ GET /api/v1/database/local-businesses?category=Dentist&country=US&hasContact=tru
|
|
|
251
255
|
|
|
252
256
|
GET /api/v1/database/ecommerce?...
|
|
253
257
|
Same pattern (including excludeDelivered and after), different dataset.
|
|
258
|
+
Technologies: technologies (runs any of), technologiesAll (runs all of), notTechnologies
|
|
259
|
+
(runs none of), each repeatable, e.g. technologiesAll=Klaviyo&technologiesAll=PayPal¬Technologies=Facebook%20Pixel
|
|
260
|
+
|
|
261
|
+
POST /api/v1/database/plan { "query": "dentists in Austin", "database": "local" }
|
|
262
|
+
(or GET /api/v1/database/plan?query=...&database=...)
|
|
263
|
+
$149/mo plan and up; planning does not count toward the daily limit. Plain English -> ready-to-run
|
|
264
|
+
database searches. query is who you want ("websites that
|
|
265
|
+
use shopify", "CEOs of SaaS companies in Texas") or what you sell ("I sell life insurance").
|
|
266
|
+
database (optional): leads | local | ecommerce; leave it out to let the planner pick.
|
|
267
|
+
Returns: { targeting, searches: [{ database, name, description, filters, count, apiEndpoint,
|
|
268
|
+
apiParams, link }] }. Filters use values the database really has; count leaves out
|
|
269
|
+
records this account already has. Send apiParams as they are to /api/v1/<apiEndpoint>.
|
|
254
270
|
|
|
255
271
|
────────────────────────────────────────
|
|
256
272
|
ERROR CODES
|
package/SKILL.md
CHANGED
|
@@ -78,9 +78,10 @@ Apollo scrapes take **up to 4 days** to deliver. Do NOT poll in a loop.
|
|
|
78
78
|
| POST | `/api/v1/scrape/cancel/{runId}` | Cancel running job |
|
|
79
79
|
| GET | `/api/v1/scrape/logs/{runId}` | Run logs |
|
|
80
80
|
| GET | `/api/v1/apollo-status` | Apollo service health: `{status: "url-based" or "legacy", message, timestamp}` |
|
|
81
|
-
| GET | `/api/v1/database/leads?title=CTO&country=United%20States&hasEmail=true&limit=100` | Lead DB ($149/mo plan and up, 100k new leads/day). Optional: `excludeDelivered=true` (only leads you don't have yet), `after=0` then `pagination.next_after` (cursor paging). Company filters: `keywords`, `revenueMin`/`revenueMax`, `companyCountry`/`companyState`/`companyCity`. Exclusions: `notTitle`, `notKeywords`, `notIndustry`. `url=<people-search URL>` searches with that URL's filters. POST with a JSON body takes the same parameters (use it for a long `url`) |
|
|
81
|
+
| GET | `/api/v1/database/leads?title=CTO&country=United%20States&hasEmail=true&limit=100` | Lead DB ($149/mo plan and up, 100k new leads/day). Optional: `excludeDelivered=true` (only leads you don't have yet), `after=0` then `pagination.next_after` (cursor paging). Company filters: `keywords`, `revenueMin`/`revenueMax`, `companyCountry`/`companyState`/`companyCity`. Exclusions: `notTitle`, `notKeywords`, `notIndustry`. Website technologies: `technologies` (any of), `technologiesAll` (all of), `notTechnologies` (none of). `url=<people-search URL>` searches with that URL's filters. POST with a JSON body takes the same parameters (use it for a long `url`) |
|
|
82
82
|
| GET | `/api/v1/database/local-businesses?category=Dentist&country=US&hasContact=true&limit=100` | Local biz DB ($149/mo plan and up, 100k new businesses/day). Search like Google Maps: `keyword` (repeat; names and categories, any keyword) and `location` (repeat; cities, states, countries, postal codes), with `searched_as` in the response and `lead_database` when nothing is found but the Lead Database has 100+ people. Filters: `category`, `country`, `state`, `city`, `postalCode`, `query`, `minRating`/`maxRating`, `minReviews`/`maxReviews`, `hasEmail`, `hasPhone`, `hasWebsite`, `hasContact` (only businesses with a named contact person), `title` (business name), `box=minLat,minLng,maxLat,maxLng` (a map area). Exclusions: `notCategory`. `url=<Google Maps search URL>` searches with that URL's keyword and place. POST with a JSON body takes the same parameters (use it for a long `url`). Same optional `excludeDelivered` / `after` |
|
|
83
|
-
| GET | `/api/v1/database/ecommerce?...` | Ecommerce DB ($149/mo plan and up). Same optional `excludeDelivered` / `after` |
|
|
83
|
+
| GET | `/api/v1/database/ecommerce?...` | Ecommerce DB ($149/mo plan and up). Same optional `excludeDelivered` / `after`. Technologies: `technologies` (any of), `technologiesAll` (all of), `notTechnologies` (none of) |
|
|
84
|
+
| POST | `/api/v1/database/plan` | $149/mo plan and up (GET with `?query=` also works). `{ "query": "dentists in Austin", "database"?: "leads" or "local" or "ecommerce" }` -> ready-to-run searches: `{ targeting, searches: [{ database, name, filters, count, apiEndpoint, apiParams, link }] }`. Send `apiParams` as they are to `/api/v1/<apiEndpoint>` |
|
|
84
85
|
|
|
85
86
|
## Status Values
|
|
86
87
|
`RUNNING` → `SUCCEEDED` or `FAILED` or `CANCELLED`
|
package/bin/cli.mjs
CHANGED
|
@@ -380,10 +380,13 @@ async function main() {
|
|
|
380
380
|
'--keywords': 'keywords', '--revenue-min': 'revenueMin', '--revenue-max': 'revenueMax',
|
|
381
381
|
'--company-country': 'companyCountry', '--company-state': 'companyState',
|
|
382
382
|
'--company-city': 'companyCity', '--not-title': 'notTitle',
|
|
383
|
-
'--not-keywords': 'notKeywords', '--not-industry': 'notIndustry'
|
|
383
|
+
'--not-keywords': 'notKeywords', '--not-industry': 'notIndustry',
|
|
384
|
+
'--technologies': 'technologies', '--technologies-all': 'technologiesAll',
|
|
385
|
+
'--not-technologies': 'notTechnologies' }
|
|
384
386
|
// List filters take commas: --country "United States,Canada" sends both.
|
|
385
387
|
const lists = new Set(['--industry', '--country', '--state', '--seniority', '--department',
|
|
386
|
-
'--company-country', '--company-state', '--not-industry'
|
|
388
|
+
'--company-country', '--company-state', '--not-industry',
|
|
389
|
+
'--technologies', '--technologies-all', '--not-technologies'])
|
|
387
390
|
for (const f of Object.keys(keyFor)) {
|
|
388
391
|
const v = flag(f)
|
|
389
392
|
if (v === undefined) continue
|
|
@@ -441,6 +444,22 @@ async function main() {
|
|
|
441
444
|
break
|
|
442
445
|
}
|
|
443
446
|
|
|
447
|
+
// ── Plan: plain English -> database searches ($149/mo plan and up) ──
|
|
448
|
+
case 'plan': {
|
|
449
|
+
const database = flag('--database')
|
|
450
|
+
const query = args.join(' ').trim()
|
|
451
|
+
if (!query) die('Usage: scrapercity plan "dentists in Austin" [--database leads|local|ecommerce]')
|
|
452
|
+
const r = await sc.planSearch(query, typeof database === 'string' ? database : undefined)
|
|
453
|
+
if (r.targeting) console.log(r.targeting)
|
|
454
|
+
for (const s of r.searches || []) {
|
|
455
|
+
console.log(`\n${s.name} (${s.database}): ${Number(s.count || 0).toLocaleString()} matches`)
|
|
456
|
+
console.log(` ${s.apiEndpoint}: ${JSON.stringify(s.apiParams)}`)
|
|
457
|
+
console.log(` ${s.link}`)
|
|
458
|
+
}
|
|
459
|
+
if (!(r.searches || []).length) console.log('No search found for that. Try naming who or what you want.')
|
|
460
|
+
break
|
|
461
|
+
}
|
|
462
|
+
|
|
444
463
|
// ── Poll (convenience) ────────────────────────────────
|
|
445
464
|
case 'poll': {
|
|
446
465
|
if (!args[0]) die('Usage: scrapercity poll <runId> [--interval 15]')
|
|
@@ -491,12 +510,17 @@ ScraperCity CLI - B2B lead generation from your terminal
|
|
|
491
510
|
scrapercity logs <runId> View run logs
|
|
492
511
|
scrapercity runs [--hours 24] List recent runs
|
|
493
512
|
|
|
513
|
+
Plan ($149/mo plan and up):
|
|
514
|
+
scrapercity plan "dentists in Austin" Plain English -> ready-to-run database searches
|
|
515
|
+
--database leads|local|ecommerce Only this database (default: the ones that fit)
|
|
516
|
+
|
|
494
517
|
Database ($149/mo plan and up):
|
|
495
518
|
scrapercity db-leads [filters] Query lead database
|
|
496
519
|
--title --industry --country --state --city --company --domain --seniority --department
|
|
497
520
|
--min-employees --max-employees --has-email --has-phone
|
|
498
521
|
--keywords --revenue-min --revenue-max --company-country --company-state --company-city
|
|
499
522
|
--not-title --not-keywords --not-industry
|
|
523
|
+
--technologies --technologies-all --not-technologies Website technologies: any of / all of / none of (comma separated)
|
|
500
524
|
--url "<people-search URL>" Search with that URL's filters
|
|
501
525
|
--exclude-delivered Only leads you don't already have
|
|
502
526
|
--after <id> Cursor paging (start with 0, then use the printed next id)
|
package/bin/mcp.mjs
CHANGED
|
@@ -48,9 +48,17 @@ const UTILITY_TOOLS = [
|
|
|
48
48
|
description: 'Cancel a running scraper job.',
|
|
49
49
|
inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID to cancel' } }, required: ['runId'] }
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
name: 'plan_search',
|
|
53
|
+
description: 'Turn a plain-English request into ready-to-run database searches. Describe who you want ("dentists in Austin", "websites that use Shopify", "CEOs of SaaS companies in Texas") or what you sell ("I sell life insurance"). Returns a few searches, each with its database, filters set to values that database really has, how many records match (leaving out what this account already has), a dashboard link, and apiParams to pass as they are to the matching query tool: query_lead_database for database/leads, query_local_business_database for database/local-businesses, query_ecommerce_database for database/ecommerce. Included with the $149/mo plan, like the database tools. Use it before a database query whenever you are not sure of the exact filter values.',
|
|
54
|
+
inputSchema: { type: 'object', properties: {
|
|
55
|
+
query: { type: 'string', description: 'What you are looking for or what you sell, in plain English' },
|
|
56
|
+
database: { type: 'string', enum: ['leads', 'local', 'ecommerce'], description: 'Optional: plan in this database only (leads = people at companies, local = local businesses, ecommerce = online stores). Leave it out to let the planner pick the databases that fit.' }
|
|
57
|
+
}, required: ['query'] }
|
|
58
|
+
},
|
|
51
59
|
{
|
|
52
60
|
name: 'query_lead_database',
|
|
53
|
-
description: 'Query the B2B lead database directly. Returns contacts with names, emails, phones, titles, companies (plus company website, revenue, HQ location and keywords). Up to 100 per request. Included with the $149/mo plan (100,000 new leads a day; leads you already have do not count again). For a daily pull of only new leads set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Have a saved people-search URL? Pass it as url to search with its filters right away (search_translation in the response says what was applied).',
|
|
61
|
+
description: 'Query the B2B lead database directly. Returns contacts with names, emails, phones, titles, companies (plus company website, revenue, HQ location and keywords). Up to 100 per request. Included with the $149/mo plan (100,000 new leads a day; leads you already have do not count again). For a daily pull of only new leads set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Have a saved people-search URL? Pass it as url to search with its filters right away (search_translation in the response says what was applied). Target by the technologies a company\'s website runs: technologies (any of), technologiesAll (all of), notTechnologies (none of), e.g. technologiesAll ["Klaviyo", "PayPal", "Shopify"] with notTechnologies ["Facebook Pixel"].',
|
|
54
62
|
inputSchema: { type: 'object', properties: {
|
|
55
63
|
url: { type: 'string', description: 'A people-search URL (the address of a people search with its filters). Its filters become the search; any other filter you pass overrides the matching one.' },
|
|
56
64
|
title: { type: 'string', description: 'Job title filter, comma separated for several (partial match, e.g. "CEO, Founder")' },
|
|
@@ -73,6 +81,9 @@ const UTILITY_TOOLS = [
|
|
|
73
81
|
notTitle: { type: 'string', description: 'Exclude titles containing any of these (comma separated)' },
|
|
74
82
|
notKeywords: { type: 'array', items: { type: 'string' }, description: 'Exclude companies with any of these keywords' },
|
|
75
83
|
notIndustry: { type: 'array', items: { type: 'string' }, description: 'Exclude these industries' },
|
|
84
|
+
technologies: { type: 'array', items: { type: 'string' }, description: 'Companies whose website runs ANY of these technologies, e.g. ["HubSpot"]' },
|
|
85
|
+
technologiesAll: { type: 'array', items: { type: 'string' }, description: 'Companies whose website runs ALL of these technologies, e.g. ["Klaviyo", "PayPal"] (keep it to 1-3)' },
|
|
86
|
+
notTechnologies: { type: 'array', items: { type: 'string' }, description: 'Leave out companies whose website runs any of these technologies, e.g. ["Facebook Pixel"]' },
|
|
76
87
|
socialUrl: { type: 'string', description: 'Match a social profile URL' },
|
|
77
88
|
hasEmail: { type: 'boolean', description: 'Only contacts with email' },
|
|
78
89
|
hasPhone: { type: 'boolean', description: 'Only contacts with phone' },
|
|
@@ -114,7 +125,7 @@ const UTILITY_TOOLS = [
|
|
|
114
125
|
},
|
|
115
126
|
{
|
|
116
127
|
name: 'query_ecommerce_database',
|
|
117
|
-
description: 'Query the ecommerce store database directly. Returns online stores with platform, estimated monthly sales and visits, employee count, emails, phones and social links. Up to 100 per request. Included with the $149/mo plan (100,000 new stores a day; stores you already have do not count again). For a daily pull of only new stores set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null.',
|
|
128
|
+
description: 'Query the ecommerce store database directly. Returns online stores with platform, estimated monthly sales and visits, employee count, emails, phones and social links. Up to 100 per request. Included with the $149/mo plan (100,000 new stores a day; stores you already have do not count again). For a daily pull of only new stores set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Combine technologies (any of), technologiesAll (all of) and notTechnologies (none of) for exact targeting, e.g. technologiesAll ["Klaviyo", "PayPal"] with notTechnologies ["Facebook Pixel"].',
|
|
118
129
|
inputSchema: { type: 'object', properties: {
|
|
119
130
|
platform: { type: 'array', items: { type: 'string' }, description: 'Ecommerce platforms, e.g. ["shopify", "woocommerce"] (any match)' },
|
|
120
131
|
country: { type: 'array', items: { type: 'string' }, description: 'Two-letter country codes, e.g. ["US"]' },
|
|
@@ -124,6 +135,8 @@ const UTILITY_TOOLS = [
|
|
|
124
135
|
merchant: { type: 'string', description: 'Merchant name (partial match)' },
|
|
125
136
|
domain: { type: 'string', description: 'Store domain(s), comma or space separated' },
|
|
126
137
|
technologies: { type: 'array', items: { type: 'string' }, description: 'Technologies the store uses (any match)' },
|
|
138
|
+
technologiesAll: { type: 'array', items: { type: 'string' }, description: 'Stores that run ALL of these technologies, e.g. ["Klaviyo", "PayPal"]' },
|
|
139
|
+
notTechnologies: { type: 'array', items: { type: 'string' }, description: 'Leave out stores that run any of these technologies, e.g. ["Facebook Pixel"]' },
|
|
127
140
|
planExclude: { type: 'array', items: { type: 'string' }, description: 'Exclude stores on these plan tiers, e.g. ["Shopify Plus"]' },
|
|
128
141
|
salesMin: { type: 'number', description: 'Minimum estimated monthly sales, USD' },
|
|
129
142
|
salesMax: { type: 'number', description: 'Maximum estimated monthly sales, USD' },
|
|
@@ -166,6 +179,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
166
179
|
break
|
|
167
180
|
}
|
|
168
181
|
case 'cancel_run': result = await sc.cancel(args.runId); break
|
|
182
|
+
case 'plan_search': result = await sc.planSearch(args.query, args.database); break
|
|
169
183
|
case 'query_lead_database': {
|
|
170
184
|
const params = { ...args }
|
|
171
185
|
if (params.hasEmail) params.hasEmail = 'true'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/generate.mjs — DO NOT EDIT BY HAND.
|
|
2
2
|
// Source of truth: scraperConfigs.ts (+ src/config/scrapers.ts). Regenerate: npm run generate
|
|
3
|
-
// Generated: 2026-10-
|
|
3
|
+
// Generated: 2026-10-20T21:18:28.662Z
|
|
4
4
|
export const TOOLS = [
|
|
5
5
|
{
|
|
6
6
|
"name": "scrape_apollo",
|
|
@@ -542,7 +542,7 @@ export const TOOLS = [
|
|
|
542
542
|
"properties": {
|
|
543
543
|
"technology": {
|
|
544
544
|
"type": "string",
|
|
545
|
-
"description": "Technology name to search for (e.g., WordPress, Shopify, HubSpot)"
|
|
545
|
+
"description": "Technology name to search for (e.g., WordPress, Shopify, HubSpot, Klaviyo). Any technology in the Tech Stack Scraper's list, including the store apps the E-commerce Database tracks."
|
|
546
546
|
},
|
|
547
547
|
"fileName": {
|
|
548
548
|
"type": "string",
|
|
@@ -661,5 +661,6 @@ export const ENDPOINTS = {
|
|
|
661
661
|
"database-enrichment": "/api/v1/enrich",
|
|
662
662
|
"database-local-business-enrichment": "/api/v1/enrich/local-businesses",
|
|
663
663
|
"database-local-businesses": "/api/v1/database/local-businesses",
|
|
664
|
-
"database-ecommerce": "/api/v1/database/ecommerce"
|
|
664
|
+
"database-ecommerce": "/api/v1/database/ecommerce",
|
|
665
|
+
"database-plan": "/api/v1/database/plan"
|
|
665
666
|
}
|
package/lib/client.mjs
CHANGED
|
@@ -208,6 +208,10 @@ export const dbEcommerce = (params) => {
|
|
|
208
208
|
return get(`${ENDPOINTS['database-ecommerce']}?${qs}`)
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// Plain English -> ready-to-run database searches ($149/mo plan and up). database: 'leads' | 'local' | 'ecommerce', or leave it out.
|
|
212
|
+
export const planSearch = (query, database) =>
|
|
213
|
+
post('/api/v1/database/plan', database ? { query, database } : { query })
|
|
214
|
+
|
|
211
215
|
// ── Poll until done ───────────────────────────────────────────
|
|
212
216
|
export async function pollUntilDone(runId, { interval = 15000, timeout = 3600000, onStatus } = {}) {
|
|
213
217
|
const start = Date.now()
|