scrapercity 1.0.17 → 1.0.19
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 +10 -0
- package/SKILL.md +1 -0
- package/bin/cli.mjs +20 -0
- package/bin/mcp.mjs +49 -0
- package/lib/catalog.generated.mjs +3 -2
- package/lib/client.mjs +4 -0
- package/package.json +1 -1
package/AGENT_DOCS.txt
CHANGED
|
@@ -252,6 +252,16 @@ GET /api/v1/database/local-businesses?category=Dentist&country=US&hasContact=tru
|
|
|
252
252
|
GET /api/v1/database/ecommerce?...
|
|
253
253
|
Same pattern (including excludeDelivered and after), different dataset.
|
|
254
254
|
|
|
255
|
+
POST /api/v1/database/plan { "query": "dentists in Austin", "database": "local" }
|
|
256
|
+
(or GET /api/v1/database/plan?query=...&database=...)
|
|
257
|
+
$149/mo plan and up; planning does not count toward the daily limit. Plain English -> ready-to-run
|
|
258
|
+
database searches. query is who you want ("websites that
|
|
259
|
+
use shopify", "CEOs of SaaS companies in Texas") or what you sell ("I sell life insurance").
|
|
260
|
+
database (optional): leads | local | ecommerce; leave it out to let the planner pick.
|
|
261
|
+
Returns: { targeting, searches: [{ database, name, description, filters, count, apiEndpoint,
|
|
262
|
+
apiParams, link }] }. Filters use values the database really has; count leaves out
|
|
263
|
+
records this account already has. Send apiParams as they are to /api/v1/<apiEndpoint>.
|
|
264
|
+
|
|
255
265
|
────────────────────────────────────────
|
|
256
266
|
ERROR CODES
|
|
257
267
|
────────────────────────────────────────
|
package/SKILL.md
CHANGED
|
@@ -81,6 +81,7 @@ Apollo scrapes take **up to 4 days** to deliver. Do NOT poll in a loop.
|
|
|
81
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`) |
|
|
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
83
|
| GET | `/api/v1/database/ecommerce?...` | Ecommerce DB ($149/mo plan and up). Same optional `excludeDelivered` / `after` |
|
|
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
|
@@ -441,6 +441,22 @@ async function main() {
|
|
|
441
441
|
break
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// ── Plan: plain English -> database searches ($149/mo plan and up) ──
|
|
445
|
+
case 'plan': {
|
|
446
|
+
const database = flag('--database')
|
|
447
|
+
const query = args.join(' ').trim()
|
|
448
|
+
if (!query) die('Usage: scrapercity plan "dentists in Austin" [--database leads|local|ecommerce]')
|
|
449
|
+
const r = await sc.planSearch(query, typeof database === 'string' ? database : undefined)
|
|
450
|
+
if (r.targeting) console.log(r.targeting)
|
|
451
|
+
for (const s of r.searches || []) {
|
|
452
|
+
console.log(`\n${s.name} (${s.database}): ${Number(s.count || 0).toLocaleString()} matches`)
|
|
453
|
+
console.log(` ${s.apiEndpoint}: ${JSON.stringify(s.apiParams)}`)
|
|
454
|
+
console.log(` ${s.link}`)
|
|
455
|
+
}
|
|
456
|
+
if (!(r.searches || []).length) console.log('No search found for that. Try naming who or what you want.')
|
|
457
|
+
break
|
|
458
|
+
}
|
|
459
|
+
|
|
444
460
|
// ── Poll (convenience) ────────────────────────────────
|
|
445
461
|
case 'poll': {
|
|
446
462
|
if (!args[0]) die('Usage: scrapercity poll <runId> [--interval 15]')
|
|
@@ -491,6 +507,10 @@ ScraperCity CLI - B2B lead generation from your terminal
|
|
|
491
507
|
scrapercity logs <runId> View run logs
|
|
492
508
|
scrapercity runs [--hours 24] List recent runs
|
|
493
509
|
|
|
510
|
+
Plan ($149/mo plan and up):
|
|
511
|
+
scrapercity plan "dentists in Austin" Plain English -> ready-to-run database searches
|
|
512
|
+
--database leads|local|ecommerce Only this database (default: the ones that fit)
|
|
513
|
+
|
|
494
514
|
Database ($149/mo plan and up):
|
|
495
515
|
scrapercity db-leads [filters] Query lead database
|
|
496
516
|
--title --industry --country --state --city --company --domain --seniority --department
|
package/bin/mcp.mjs
CHANGED
|
@@ -48,6 +48,14 @@ 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
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).',
|
|
@@ -111,6 +119,36 @@ const UTILITY_TOOLS = [
|
|
|
111
119
|
excludeDelivered: { type: 'boolean', description: 'Skip businesses this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
|
|
112
120
|
after: { type: 'string', description: 'Cursor paging: return businesses after this id. Use "0" to start, then the pagination.next_after from each response.' }
|
|
113
121
|
} }
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'query_ecommerce_database',
|
|
125
|
+
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.',
|
|
126
|
+
inputSchema: { type: 'object', properties: {
|
|
127
|
+
platform: { type: 'array', items: { type: 'string' }, description: 'Ecommerce platforms, e.g. ["shopify", "woocommerce"] (any match)' },
|
|
128
|
+
country: { type: 'array', items: { type: 'string' }, description: 'Two-letter country codes, e.g. ["US"]' },
|
|
129
|
+
state: { type: 'string', description: 'State (partial match)' },
|
|
130
|
+
city: { type: 'string', description: 'City (partial match)' },
|
|
131
|
+
category: { type: 'array', items: { type: 'string' }, description: 'Store categories (any match)' },
|
|
132
|
+
merchant: { type: 'string', description: 'Merchant name (partial match)' },
|
|
133
|
+
domain: { type: 'string', description: 'Store domain(s), comma or space separated' },
|
|
134
|
+
technologies: { type: 'array', items: { type: 'string' }, description: 'Technologies the store uses (any match)' },
|
|
135
|
+
planExclude: { type: 'array', items: { type: 'string' }, description: 'Exclude stores on these plan tiers, e.g. ["Shopify Plus"]' },
|
|
136
|
+
salesMin: { type: 'number', description: 'Minimum estimated monthly sales, USD' },
|
|
137
|
+
salesMax: { type: 'number', description: 'Maximum estimated monthly sales, USD' },
|
|
138
|
+
visitsMin: { type: 'number', description: 'Minimum estimated monthly visits' },
|
|
139
|
+
visitsMax: { type: 'number', description: 'Maximum estimated monthly visits' },
|
|
140
|
+
employeesMin: { type: 'number', description: 'Minimum employee count' },
|
|
141
|
+
employeesMax: { type: 'number', description: 'Maximum employee count' },
|
|
142
|
+
hasEmail: { type: 'boolean', description: 'Only stores with an email address' },
|
|
143
|
+
hasPhone: { type: 'boolean', description: 'Only stores with a phone number' },
|
|
144
|
+
hasInstagram: { type: 'boolean', description: 'Only stores with Instagram' },
|
|
145
|
+
hasFacebook: { type: 'boolean', description: 'Only stores with Facebook' },
|
|
146
|
+
hasTiktok: { type: 'boolean', description: 'Only stores with TikTok' },
|
|
147
|
+
page: { type: 'number', description: 'Page number (default 1)', default: 1 },
|
|
148
|
+
limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
|
|
149
|
+
excludeDelivered: { type: 'boolean', description: 'Skip stores this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
|
|
150
|
+
after: { type: 'string', description: 'Cursor paging: return stores after this id. Use "0" to start, then the pagination.next_after from each response.' }
|
|
151
|
+
} }
|
|
114
152
|
}
|
|
115
153
|
]
|
|
116
154
|
|
|
@@ -136,6 +174,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
136
174
|
break
|
|
137
175
|
}
|
|
138
176
|
case 'cancel_run': result = await sc.cancel(args.runId); break
|
|
177
|
+
case 'plan_search': result = await sc.planSearch(args.query, args.database); break
|
|
139
178
|
case 'query_lead_database': {
|
|
140
179
|
const params = { ...args }
|
|
141
180
|
if (params.hasEmail) params.hasEmail = 'true'
|
|
@@ -156,6 +195,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
156
195
|
result = await sc.dbLocalBusinesses(params)
|
|
157
196
|
break
|
|
158
197
|
}
|
|
198
|
+
case 'query_ecommerce_database': {
|
|
199
|
+
const params = { ...args }
|
|
200
|
+
for (const k of ['hasEmail', 'hasPhone', 'hasInstagram', 'hasFacebook', 'hasTiktok', 'excludeDelivered']) {
|
|
201
|
+
if (params[k]) params[k] = 'true'
|
|
202
|
+
else delete params[k]
|
|
203
|
+
}
|
|
204
|
+
if (params.after !== undefined && params.after !== null) params.after = String(params.after)
|
|
205
|
+
result = await sc.dbEcommerce(params)
|
|
206
|
+
break
|
|
207
|
+
}
|
|
159
208
|
default:
|
|
160
209
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
|
|
161
210
|
}
|
|
@@ -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-19T21:46:11.092Z
|
|
4
4
|
export const TOOLS = [
|
|
5
5
|
{
|
|
6
6
|
"name": "scrape_apollo",
|
|
@@ -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()
|