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 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-09T16:22:32.237Z
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()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapercity",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "ScraperCity CLI & MCP Server - B2B lead generation for AI agents",
5
5
  "type": "module",
6
6
  "bin": {