scrapercity 1.0.21 → 1.0.23
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 +12 -0
- package/SKILL.md +1 -0
- package/bin/cli.mjs +46 -0
- package/bin/mcp.mjs +29 -0
- package/lib/catalog.generated.mjs +3 -2
- package/lib/client.mjs +22 -0
- package/package.json +1 -1
package/AGENT_DOCS.txt
CHANGED
|
@@ -268,6 +268,18 @@ POST /api/v1/database/plan { "query": "dentists in Austin", "database": "local"
|
|
|
268
268
|
apiParams, link }] }. Filters use values the database really has; count leaves out
|
|
269
269
|
records this account already has. Send apiParams as they are to /api/v1/<apiEndpoint>.
|
|
270
270
|
|
|
271
|
+
POST /api/v1/autoleads/lookalike { "domains": ["acme.com", "mike@globex.com", "initech.com"] }
|
|
272
|
+
GET /api/v1/autoleads/lookalike?id=<job id>
|
|
273
|
+
$149/mo plan and up. Your customers' websites or work emails -> lookalike groups. POST starts a
|
|
274
|
+
background job (202 { job }); GET it until job.status is done (or failed). When done, job.groups:
|
|
275
|
+
[{ name, description, sites, database, filters, count, tool, addOn, reason }]. Send a group's
|
|
276
|
+
filters to that database's endpoint to pull the records. $0.01 per site read, from credits when the
|
|
277
|
+
job finishes; sites that can't be read are free. 402 = balance under $0.01 x every site (required,
|
|
278
|
+
available). 409 = a job is already running (job_id). At least 3 company sites; personal emails skipped.
|
|
279
|
+
GET /api/v1/autoleads/lookalike?id=<job id>&format=csv returns the finished job as a CSV, one row per
|
|
280
|
+
site: Domain, Read, Group, Sells, Sells To, Business Model, Industry, Keywords, Size, Country,
|
|
281
|
+
Contact Emails, Contact Titles. No extra charge.
|
|
282
|
+
|
|
271
283
|
────────────────────────────────────────
|
|
272
284
|
ERROR CODES
|
|
273
285
|
────────────────────────────────────────
|
package/SKILL.md
CHANGED
|
@@ -82,6 +82,7 @@ Apollo scrapes take **up to 4 days** to deliver. Do NOT poll in a loop.
|
|
|
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`. Technologies: `technologies` (any of), `technologiesAll` (all of), `notTechnologies` (none of) |
|
|
84
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>` |
|
|
85
|
+
| POST | `/api/v1/autoleads/lookalike` | $149/mo plan and up, $0.01 per site read. `{ "domains": ["acme.com", "mike@globex.com", ...] }` (websites or work emails, at least 3 company sites) -> 202 `{ job }`. Then GET `/api/v1/autoleads/lookalike?id=<job id>` until `job.status` is `done`: `job.groups` = `[{ name, description, sites, database, filters, count, tool }]`. Send a group's `filters` to that database's endpoint. 402 = balance under $0.01 x every site; 409 = a job is already running. Add `&format=csv` to the GET for every site as a CSV (no extra charge) |
|
|
85
86
|
|
|
86
87
|
## Status Values
|
|
87
88
|
`RUNNING` → `SUCCEEDED` or `FAILED` or `CANCELLED`
|
package/bin/cli.mjs
CHANGED
|
@@ -460,6 +460,46 @@ async function main() {
|
|
|
460
460
|
break
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
// ── Lookalike groups from your customers ($149/mo plan and up) ──
|
|
464
|
+
case 'lookalike': {
|
|
465
|
+
const file = flag('--file')
|
|
466
|
+
let domains = [...args]
|
|
467
|
+
if (typeof file === 'string') domains = [...domains, ...fs.readFileSync(file, 'utf-8').split(/[\n,;\t]+/).map(l => l.trim()).filter(Boolean)]
|
|
468
|
+
if (!domains.length) die('Usage: scrapercity lookalike acme.com mike@globex.com ... [--file customers.txt]')
|
|
469
|
+
const start = await sc.lookalikeStart(domains)
|
|
470
|
+
let job = start.job
|
|
471
|
+
console.log(`Job ${job.id}: ${job.sites} sites, up to $${Number(job.max_cost).toFixed(2)}`)
|
|
472
|
+
while (job.status === 'running') {
|
|
473
|
+
await new Promise(r => setTimeout(r, 10000))
|
|
474
|
+
job = (await sc.lookalikeJob(job.id)).job
|
|
475
|
+
process.stdout.write(`\r ${job.stage} ${job.percent}% `)
|
|
476
|
+
}
|
|
477
|
+
console.log('')
|
|
478
|
+
if (job.status !== 'done') die(job.error || 'The job did not finish.')
|
|
479
|
+
console.log(`${job.sites_read} of ${job.sites} sites read, $${Number(job.charged).toFixed(2)} charged, ${job.groups.length} groups`)
|
|
480
|
+
for (const g of job.groups) {
|
|
481
|
+
console.log(`\n${g.name} (${g.database}): ${Number(g.count).toLocaleString()} matches, ${g.sites.length} of your customers`)
|
|
482
|
+
console.log(` ${g.description}`)
|
|
483
|
+
console.log(` Best tool: ${g.tool}${g.addOn ? ' + ' + g.addOn : ''}`)
|
|
484
|
+
console.log(` filters: ${JSON.stringify(g.filters)}`)
|
|
485
|
+
}
|
|
486
|
+
break
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
case 'lookalike-csv': {
|
|
490
|
+
const out = flag('-o') || flag('--output')
|
|
491
|
+
if (!args[0]) die('Usage: scrapercity lookalike-csv <jobId> [-o lookalike.csv]')
|
|
492
|
+
const dl = await sc.lookalikeCsv(args[0], typeof out === 'string' ? out : undefined)
|
|
493
|
+
console.log(`✓ Saved ${dl.path} (${Math.round(dl.bytes / 1024)} KB)`)
|
|
494
|
+
break
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
case 'lookalike-status': {
|
|
498
|
+
if (!args[0]) die('Usage: scrapercity lookalike-status <jobId>')
|
|
499
|
+
console.log(json((await sc.lookalikeJob(args[0])).job))
|
|
500
|
+
break
|
|
501
|
+
}
|
|
502
|
+
|
|
463
503
|
// ── Poll (convenience) ────────────────────────────────
|
|
464
504
|
case 'poll': {
|
|
465
505
|
if (!args[0]) die('Usage: scrapercity poll <runId> [--interval 15]')
|
|
@@ -514,6 +554,12 @@ ScraperCity CLI - B2B lead generation from your terminal
|
|
|
514
554
|
scrapercity plan "dentists in Austin" Plain English -> ready-to-run database searches
|
|
515
555
|
--database leads|local|ecommerce Only this database (default: the ones that fit)
|
|
516
556
|
|
|
557
|
+
Lookalike groups ($149/mo plan and up, $0.01 per site read):
|
|
558
|
+
scrapercity lookalike acme.com mike@globex.com ... Group your customers into lookalike audiences
|
|
559
|
+
--file customers.txt One website or work email per line
|
|
560
|
+
scrapercity lookalike-status <jobId> Read a lookalike job
|
|
561
|
+
scrapercity lookalike-csv <jobId> [-o f.csv] Every site of a finished job as a CSV
|
|
562
|
+
|
|
517
563
|
Database ($149/mo plan and up):
|
|
518
564
|
scrapercity db-leads [filters] Query lead database
|
|
519
565
|
--title --industry --country --state --city --company --domain --seniority --department
|
package/bin/mcp.mjs
CHANGED
|
@@ -56,6 +56,28 @@ const UTILITY_TOOLS = [
|
|
|
56
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
57
|
}, required: ['query'] }
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
name: 'find_lookalike_groups',
|
|
61
|
+
description: 'Group your customers into lookalike audiences. Send their websites or work emails (mike@acme.com reads acme.com and looks up Mike\'s title). Starts a background job and returns it with its id; then call get_lookalike_groups until status is done. Each group comes with ready-to-run database filters, a match count and the best tool to reach businesses like them. $0.01 per site read, from credits; your balance must cover $0.01 x every site. One job at a time. $149/mo plan and up.',
|
|
62
|
+
inputSchema: { type: 'object', properties: {
|
|
63
|
+
domains: { type: 'array', items: { type: 'string' }, description: 'Your customers\' websites, URLs or work emails. At least 3 company sites.' }
|
|
64
|
+
}, required: ['domains'] }
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'get_lookalike_groups',
|
|
68
|
+
description: 'Read a lookalike job started with find_lookalike_groups: status (running, done, failed), percent, and when done the groups. Each group has name, description, sites (your customers in it), database (leads, local, ecommerce), filters, count and tool. Pass its filters to query_lead_database, query_local_business_database or query_ecommerce_database to pull the records.',
|
|
69
|
+
inputSchema: { type: 'object', properties: {
|
|
70
|
+
id: { type: 'string', description: 'The job id from find_lookalike_groups' }
|
|
71
|
+
}, required: ['id'] }
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'download_lookalike_csv',
|
|
75
|
+
description: 'Save a finished lookalike job (find_lookalike_groups) as a CSV file, one row per uploaded site: whether it was read, what it sells and to whom, business model, industry, keywords, size, country, the contacts from your upload with their titles, and its group. No extra charge.',
|
|
76
|
+
inputSchema: { type: 'object', properties: {
|
|
77
|
+
id: { type: 'string', description: 'The job id from find_lookalike_groups' },
|
|
78
|
+
outputPath: { type: 'string', description: 'File path to save the CSV (default: lookalike-{id}.csv)' }
|
|
79
|
+
}, required: ['id'] }
|
|
80
|
+
},
|
|
59
81
|
{
|
|
60
82
|
name: 'query_lead_database',
|
|
61
83
|
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"].',
|
|
@@ -180,6 +202,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
180
202
|
}
|
|
181
203
|
case 'cancel_run': result = await sc.cancel(args.runId); break
|
|
182
204
|
case 'plan_search': result = await sc.planSearch(args.query, args.database); break
|
|
205
|
+
case 'find_lookalike_groups': result = await sc.lookalikeStart(args.domains); break
|
|
206
|
+
case 'get_lookalike_groups': result = await sc.lookalikeJob(args.id); break
|
|
207
|
+
case 'download_lookalike_csv': {
|
|
208
|
+
const dl = await sc.lookalikeCsv(args.id, args.outputPath)
|
|
209
|
+
result = { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
|
|
210
|
+
break
|
|
211
|
+
}
|
|
183
212
|
case 'query_lead_database': {
|
|
184
213
|
const params = { ...args }
|
|
185
214
|
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-27T22:10:20.665Z
|
|
4
4
|
export const TOOLS = [
|
|
5
5
|
{
|
|
6
6
|
"name": "scrape_apollo",
|
|
@@ -662,5 +662,6 @@ export const ENDPOINTS = {
|
|
|
662
662
|
"database-local-business-enrichment": "/api/v1/enrich/local-businesses",
|
|
663
663
|
"database-local-businesses": "/api/v1/database/local-businesses",
|
|
664
664
|
"database-ecommerce": "/api/v1/database/ecommerce",
|
|
665
|
-
"database-plan": "/api/v1/database/plan"
|
|
665
|
+
"database-plan": "/api/v1/database/plan",
|
|
666
|
+
"autoleads-lookalike": "/api/v1/autoleads/lookalike"
|
|
666
667
|
}
|
package/lib/client.mjs
CHANGED
|
@@ -212,6 +212,28 @@ export const dbEcommerce = (params) => {
|
|
|
212
212
|
export const planSearch = (query, database) =>
|
|
213
213
|
post('/api/v1/database/plan', database ? { query, database } : { query })
|
|
214
214
|
|
|
215
|
+
// Lookalike groups from your customers ($149/mo plan and up): start a job with websites or work emails, then read it by id.
|
|
216
|
+
export const lookalikeStart = (domains) => post('/api/v1/autoleads/lookalike', { domains })
|
|
217
|
+
export const lookalikeJob = (id) => get(`/api/v1/autoleads/lookalike?id=${encodeURIComponent(id)}`)
|
|
218
|
+
|
|
219
|
+
// A finished lookalike job as a CSV file, one row per site (what was read, contacts, group).
|
|
220
|
+
export async function lookalikeCsv(id, outputPath) {
|
|
221
|
+
const key = getApiKey()
|
|
222
|
+
if (!key) throw new Error('No API key. Run: scrapercity login OR export SCRAPERCITY_API_KEY=...')
|
|
223
|
+
const res = await fetch(`${BASE}/api/v1/autoleads/lookalike?id=${encodeURIComponent(id)}&format=csv`, {
|
|
224
|
+
headers: { 'Authorization': `Bearer ${key}`, 'User-Agent': 'scrapercity-cli/1.0' }
|
|
225
|
+
})
|
|
226
|
+
if (!res.ok) {
|
|
227
|
+
let msg = `HTTP ${res.status}`
|
|
228
|
+
try { msg = (await res.json()).error || msg } catch {}
|
|
229
|
+
throw new Error(msg)
|
|
230
|
+
}
|
|
231
|
+
const buf = Buffer.from(await res.arrayBuffer())
|
|
232
|
+
const out = outputPath || `lookalike-${id}.csv`
|
|
233
|
+
fs.writeFileSync(out, buf)
|
|
234
|
+
return { path: out, bytes: buf.length }
|
|
235
|
+
}
|
|
236
|
+
|
|
215
237
|
// ── Poll until done ───────────────────────────────────────────
|
|
216
238
|
export async function pollUntilDone(runId, { interval = 15000, timeout = 3600000, onStatus } = {}) {
|
|
217
239
|
const start = Date.now()
|