enrich-companies 1.0.0
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/README.md +111 -0
- package/index.js +279 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# enrich-csv
|
|
2
|
+
|
|
3
|
+
Enrich a CSV of companies with revenue, credit score, employees, and financial data. Powered by the [Score API](https://score.get-scala.com/api) — 272M+ companies from 265 countries.
|
|
4
|
+
|
|
5
|
+
**The Clearbit / ZoomInfo / D&B alternative that costs €0 to start.**
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx enrich-csv leads.csv -o enriched.csv
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That's it. No signup needed. Free tier: 50 lookups/month.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g enrich-csv
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or use directly with `npx` (no install needed):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx enrich-csv input.csv -o output.csv
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Basic: enrich a CSV file
|
|
31
|
+
enrich-csv companies.csv -o enriched.csv
|
|
32
|
+
|
|
33
|
+
# Filter by country
|
|
34
|
+
enrich-csv leads.csv --country IT -o italian-leads.csv
|
|
35
|
+
|
|
36
|
+
# Specify which column contains company names
|
|
37
|
+
enrich-csv data.csv --column "Company Name" -o enriched.csv
|
|
38
|
+
|
|
39
|
+
# Choose which fields to add
|
|
40
|
+
enrich-csv leads.csv --fields revenue,score,grade,employees -o enriched.csv
|
|
41
|
+
|
|
42
|
+
# Use with API key for higher limits
|
|
43
|
+
enrich-csv leads.csv -k sk_live_your_key -o enriched.csv
|
|
44
|
+
|
|
45
|
+
# Pipe from stdin
|
|
46
|
+
cat companies.txt | enrich-csv - -o results.csv
|
|
47
|
+
|
|
48
|
+
# Dry run (see what would be enriched)
|
|
49
|
+
enrich-csv leads.csv --dry-run
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What Gets Added
|
|
53
|
+
|
|
54
|
+
For each company in your CSV, enrich-csv adds:
|
|
55
|
+
|
|
56
|
+
| Field | Description |
|
|
57
|
+
|-------|-------------|
|
|
58
|
+
| `score_revenue` | Annual revenue (EUR) |
|
|
59
|
+
| `score_employees` | Employee count |
|
|
60
|
+
| `score_score` | Credit score 0-100 |
|
|
61
|
+
| `score_grade` | Letter grade (AA to E) |
|
|
62
|
+
| `score_country` | ISO country code |
|
|
63
|
+
| `score_city` | City |
|
|
64
|
+
| `score_sector_desc` | Industry description |
|
|
65
|
+
|
|
66
|
+
## Example
|
|
67
|
+
|
|
68
|
+
**Input (leads.csv):**
|
|
69
|
+
```csv
|
|
70
|
+
Company,Contact,Email
|
|
71
|
+
Tesla,John,john@tesla.com
|
|
72
|
+
Ferrero,Maria,maria@ferrero.com
|
|
73
|
+
SAP,Hans,hans@sap.com
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Output (enriched.csv):**
|
|
77
|
+
```csv
|
|
78
|
+
Company,Contact,Email,score_revenue,score_employees,score_score,score_grade,score_country,score_city,score_sector_desc
|
|
79
|
+
Tesla,John,john@tesla.com,96773000000,140000,85,A,US,Austin,Motor Vehicle Manufacturing
|
|
80
|
+
Ferrero,Maria,maria@ferrero.com,19300000000,48697,75,BBB,IT,Alba,Food Manufacturing
|
|
81
|
+
SAP,Hans,hans@sap.com,35000000000,107000,88,A,DE,Walldorf,Software Publishing
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API Pricing
|
|
85
|
+
|
|
86
|
+
| Plan | Lookups/month | Price |
|
|
87
|
+
|------|--------------|-------|
|
|
88
|
+
| Free | 50 | €0 |
|
|
89
|
+
| Starter | 500 | €19/mo |
|
|
90
|
+
| Growth | 5,000 | €49/mo |
|
|
91
|
+
| Enterprise | 50,000 | €149/mo |
|
|
92
|
+
|
|
93
|
+
Get your free API key: https://score.get-scala.com/api/free-key
|
|
94
|
+
|
|
95
|
+
## Data Coverage
|
|
96
|
+
|
|
97
|
+
272,116,630 companies across 265 countries. Top coverage:
|
|
98
|
+
|
|
99
|
+
🇧🇷 Brazil 47M · 🇺🇸 USA 39M · 🇦🇺 Australia 20M · 🇫🇷 France 17M · 🇬🇧 UK 14M · 🇩🇪 Germany 10M · 🇮🇳 India 8M · 🇯🇵 Japan 7M · 🇮🇹 Italy 6M · 🇪🇸 Spain 5M
|
|
100
|
+
|
|
101
|
+
## Related
|
|
102
|
+
|
|
103
|
+
- [Score API](https://score.get-scala.com/api) — Full REST API
|
|
104
|
+
- [scala-score](https://www.npmjs.com/package/scala-score) — Node.js SDK
|
|
105
|
+
- [scala-mcp-server](https://www.npmjs.com/package/scala-mcp-server) — MCP server for AI agents
|
|
106
|
+
- [Dataset on Kaggle](https://www.kaggle.com/datasets/yorkiealfbroth/global-company-data-994k) — Free 994K sample
|
|
107
|
+
- [Dataset on HuggingFace](https://huggingface.co/datasets/alf1990mi/global-company-database-1m) — Free 1M sample
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT — Built by [SCALA AI](https://get-scala.com)
|
package/index.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const VERSION = '1.0.0';
|
|
8
|
+
const API_BASE = 'https://score.get-scala.com/api/search';
|
|
9
|
+
const RATE_LIMIT_MS = 200;
|
|
10
|
+
|
|
11
|
+
function usage() {
|
|
12
|
+
console.log(`
|
|
13
|
+
enrich-csv v${VERSION} — Enrich company data from 272M+ businesses
|
|
14
|
+
|
|
15
|
+
USAGE:
|
|
16
|
+
enrich-csv input.csv [options]
|
|
17
|
+
cat companies.csv | enrich-csv - [options]
|
|
18
|
+
|
|
19
|
+
OPTIONS:
|
|
20
|
+
-o, --output FILE Output file (default: stdout)
|
|
21
|
+
-k, --key KEY Score API key (or set SCORE_API_KEY env var)
|
|
22
|
+
-c, --column NAME Column name containing company names (default: auto-detect)
|
|
23
|
+
--country CC Filter by ISO country code (e.g., IT, US, DE)
|
|
24
|
+
--delimiter CHAR CSV delimiter (default: auto-detect , or ;)
|
|
25
|
+
--limit N Max results per lookup (default: 1)
|
|
26
|
+
--fields FIELDS Fields to add (default: revenue,employees,score,grade,country,city,sector_desc)
|
|
27
|
+
--dry-run Show what would be enriched without calling API
|
|
28
|
+
-h, --help Show this help
|
|
29
|
+
-v, --version Show version
|
|
30
|
+
|
|
31
|
+
EXAMPLES:
|
|
32
|
+
enrich-csv leads.csv -o enriched.csv
|
|
33
|
+
enrich-csv leads.csv --country IT --key sk_live_xxx
|
|
34
|
+
enrich-csv leads.csv --column "Company Name" --fields revenue,score,grade
|
|
35
|
+
cat names.txt | enrich-csv - -o results.csv
|
|
36
|
+
|
|
37
|
+
FREE TIER: 50 lookups/month (no key needed)
|
|
38
|
+
Get API key: https://score.get-scala.com/api/free-key
|
|
39
|
+
|
|
40
|
+
PRICING:
|
|
41
|
+
Free 50 lookups/month €0
|
|
42
|
+
Starter 500 lookups/month €19/mo
|
|
43
|
+
Growth 5,000 lookups/month €49/mo
|
|
44
|
+
Enterprise 50,000 lookups/mo €149/mo
|
|
45
|
+
|
|
46
|
+
More info: https://score.get-scala.com/api
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseArgs() {
|
|
51
|
+
const args = process.argv.slice(2);
|
|
52
|
+
const opts = {
|
|
53
|
+
input: null,
|
|
54
|
+
output: null,
|
|
55
|
+
key: process.env.SCORE_API_KEY || null,
|
|
56
|
+
column: null,
|
|
57
|
+
country: null,
|
|
58
|
+
delimiter: null,
|
|
59
|
+
limit: 1,
|
|
60
|
+
fields: ['revenue', 'employees', 'score', 'grade', 'country', 'city', 'sector_desc'],
|
|
61
|
+
dryRun: false,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
const a = args[i];
|
|
66
|
+
if (a === '-h' || a === '--help') { usage(); process.exit(0); }
|
|
67
|
+
if (a === '-v' || a === '--version') { console.log(VERSION); process.exit(0); }
|
|
68
|
+
if (a === '-o' || a === '--output') { opts.output = args[++i]; continue; }
|
|
69
|
+
if (a === '-k' || a === '--key') { opts.key = args[++i]; continue; }
|
|
70
|
+
if (a === '-c' || a === '--column') { opts.column = args[++i]; continue; }
|
|
71
|
+
if (a === '--country') { opts.country = args[++i]; continue; }
|
|
72
|
+
if (a === '--delimiter') { opts.delimiter = args[++i]; continue; }
|
|
73
|
+
if (a === '--limit') { opts.limit = parseInt(args[++i], 10); continue; }
|
|
74
|
+
if (a === '--fields') { opts.fields = args[++i].split(','); continue; }
|
|
75
|
+
if (a === '--dry-run') { opts.dryRun = true; continue; }
|
|
76
|
+
if (!opts.input) { opts.input = a; continue; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return opts;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function detectDelimiter(line) {
|
|
83
|
+
const semicolons = (line.match(/;/g) || []).length;
|
|
84
|
+
const commas = (line.match(/,/g) || []).length;
|
|
85
|
+
const tabs = (line.match(/\t/g) || []).length;
|
|
86
|
+
if (tabs > commas && tabs > semicolons) return '\t';
|
|
87
|
+
if (semicolons > commas) return ';';
|
|
88
|
+
return ',';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseCSVLine(line, delimiter) {
|
|
92
|
+
const fields = [];
|
|
93
|
+
let current = '';
|
|
94
|
+
let inQuotes = false;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < line.length; i++) {
|
|
97
|
+
const c = line[i];
|
|
98
|
+
if (c === '"') {
|
|
99
|
+
if (inQuotes && line[i + 1] === '"') { current += '"'; i++; }
|
|
100
|
+
else { inQuotes = !inQuotes; }
|
|
101
|
+
} else if (c === delimiter && !inQuotes) {
|
|
102
|
+
fields.push(current.trim());
|
|
103
|
+
current = '';
|
|
104
|
+
} else {
|
|
105
|
+
current += c;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
fields.push(current.trim());
|
|
109
|
+
return fields;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function escapeCSV(val, delimiter) {
|
|
113
|
+
if (val == null) return '';
|
|
114
|
+
const s = String(val);
|
|
115
|
+
if (s.includes(delimiter) || s.includes('"') || s.includes('\n')) {
|
|
116
|
+
return '"' + s.replace(/"/g, '""') + '"';
|
|
117
|
+
}
|
|
118
|
+
return s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function detectNameColumn(headers) {
|
|
122
|
+
const patterns = [
|
|
123
|
+
/^company$/i, /^company.?name$/i, /^name$/i, /^business$/i,
|
|
124
|
+
/^business.?name$/i, /^organization$/i, /^org$/i, /^azienda$/i,
|
|
125
|
+
/^ragione.?sociale$/i, /^empresa$/i, /^entreprise$/i, /^firma$/i,
|
|
126
|
+
/^unternehmen$/i, /^nome$/i, /^denominazione$/i,
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
for (const p of patterns) {
|
|
130
|
+
const idx = headers.findIndex(h => p.test(h));
|
|
131
|
+
if (idx >= 0) return idx;
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function apiSearch(query, country, key, limit) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
let url = `${API_BASE}?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
139
|
+
if (country) url += `&country=${country}`;
|
|
140
|
+
if (key) url += `&key=${key}`;
|
|
141
|
+
|
|
142
|
+
https.get(url, { headers: { 'User-Agent': `enrich-csv/${VERSION}` } }, (res) => {
|
|
143
|
+
let data = '';
|
|
144
|
+
res.on('data', c => data += c);
|
|
145
|
+
res.on('end', () => {
|
|
146
|
+
try {
|
|
147
|
+
const json = JSON.parse(data);
|
|
148
|
+
if (json.results && json.results.length > 0) {
|
|
149
|
+
resolve(json.results[0]);
|
|
150
|
+
} else {
|
|
151
|
+
resolve(null);
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
resolve(null);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}).on('error', () => resolve(null));
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
162
|
+
|
|
163
|
+
function formatRevenue(val) {
|
|
164
|
+
if (!val || val === 0) return '';
|
|
165
|
+
if (val >= 1e9) return `€${(val / 1e9).toFixed(1)}B`;
|
|
166
|
+
if (val >= 1e6) return `€${(val / 1e6).toFixed(1)}M`;
|
|
167
|
+
if (val >= 1e3) return `€${(val / 1e3).toFixed(0)}K`;
|
|
168
|
+
return `€${val}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function main() {
|
|
172
|
+
const opts = parseArgs();
|
|
173
|
+
|
|
174
|
+
if (!opts.input) {
|
|
175
|
+
console.error('Error: no input file specified. Use --help for usage.');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let inputData;
|
|
180
|
+
if (opts.input === '-') {
|
|
181
|
+
const chunks = [];
|
|
182
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
183
|
+
inputData = Buffer.concat(chunks).toString('utf-8');
|
|
184
|
+
} else {
|
|
185
|
+
if (!fs.existsSync(opts.input)) {
|
|
186
|
+
console.error(`Error: file not found: ${opts.input}`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
inputData = fs.readFileSync(opts.input, 'utf-8');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const lines = inputData.split('\n').filter(l => l.trim());
|
|
193
|
+
if (lines.length < 2) {
|
|
194
|
+
console.error('Error: CSV must have a header row and at least one data row.');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const delimiter = opts.delimiter || detectDelimiter(lines[0]);
|
|
199
|
+
const headers = parseCSVLine(lines[0], delimiter);
|
|
200
|
+
const nameColIdx = opts.column
|
|
201
|
+
? headers.findIndex(h => h.toLowerCase() === opts.column.toLowerCase())
|
|
202
|
+
: detectNameColumn(headers);
|
|
203
|
+
|
|
204
|
+
if (nameColIdx < 0) {
|
|
205
|
+
console.error(`Error: column "${opts.column}" not found. Available: ${headers.join(', ')}`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const enrichedHeaders = [...headers, ...opts.fields.map(f => `score_${f}`)];
|
|
210
|
+
const outputLines = [enrichedHeaders.map(h => escapeCSV(h, delimiter)).join(delimiter)];
|
|
211
|
+
|
|
212
|
+
const total = lines.length - 1;
|
|
213
|
+
let enriched = 0;
|
|
214
|
+
let notFound = 0;
|
|
215
|
+
|
|
216
|
+
process.stderr.write(`\nEnriching ${total} companies from column "${headers[nameColIdx]}"...\n`);
|
|
217
|
+
if (!opts.key) {
|
|
218
|
+
process.stderr.write('No API key — using free tier (50 lookups/month)\n');
|
|
219
|
+
process.stderr.write('Get unlimited: https://score.get-scala.com/api/free-key\n');
|
|
220
|
+
}
|
|
221
|
+
process.stderr.write('\n');
|
|
222
|
+
|
|
223
|
+
for (let i = 1; i < lines.length; i++) {
|
|
224
|
+
const row = parseCSVLine(lines[i], delimiter);
|
|
225
|
+
const companyName = row[nameColIdx];
|
|
226
|
+
|
|
227
|
+
if (!companyName || !companyName.trim()) {
|
|
228
|
+
const emptyRow = [...row, ...opts.fields.map(() => '')];
|
|
229
|
+
outputLines.push(emptyRow.map(v => escapeCSV(v, delimiter)).join(delimiter));
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
process.stderr.write(` [${i}/${total}] ${companyName.substring(0, 40).padEnd(40)} `);
|
|
234
|
+
|
|
235
|
+
if (opts.dryRun) {
|
|
236
|
+
process.stderr.write('(dry run)\n');
|
|
237
|
+
const emptyRow = [...row, ...opts.fields.map(() => '')];
|
|
238
|
+
outputLines.push(emptyRow.map(v => escapeCSV(v, delimiter)).join(delimiter));
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const result = await apiSearch(companyName, opts.country, opts.key, opts.limit);
|
|
243
|
+
|
|
244
|
+
if (result) {
|
|
245
|
+
enriched++;
|
|
246
|
+
const enrichedValues = opts.fields.map(f => {
|
|
247
|
+
if (f === 'revenue') return result.revenue || '';
|
|
248
|
+
return result[f] != null ? result[f] : '';
|
|
249
|
+
});
|
|
250
|
+
const enrichedRow = [...row, ...enrichedValues];
|
|
251
|
+
outputLines.push(enrichedRow.map(v => escapeCSV(v, delimiter)).join(delimiter));
|
|
252
|
+
process.stderr.write(`✓ ${result.name} (${result.country}) rev=${formatRevenue(result.revenue)}\n`);
|
|
253
|
+
} else {
|
|
254
|
+
notFound++;
|
|
255
|
+
const emptyRow = [...row, ...opts.fields.map(() => '')];
|
|
256
|
+
outputLines.push(emptyRow.map(v => escapeCSV(v, delimiter)).join(delimiter));
|
|
257
|
+
process.stderr.write('✗ not found\n');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await sleep(RATE_LIMIT_MS);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const output = outputLines.join('\n') + '\n';
|
|
264
|
+
|
|
265
|
+
if (opts.output) {
|
|
266
|
+
fs.writeFileSync(opts.output, output, 'utf-8');
|
|
267
|
+
process.stderr.write(`\nDone! ${enriched}/${total} enriched, ${notFound} not found.\n`);
|
|
268
|
+
process.stderr.write(`Output: ${opts.output}\n`);
|
|
269
|
+
} else {
|
|
270
|
+
process.stdout.write(output);
|
|
271
|
+
process.stderr.write(`\n${enriched}/${total} enriched, ${notFound} not found.\n`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
process.stderr.write(`\n--- Score API by SCALA AI ---\n`);
|
|
275
|
+
process.stderr.write(`272M+ companies | 265 countries | Free tier available\n`);
|
|
276
|
+
process.stderr.write(`https://score.get-scala.com/api\n\n`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
main().catch(e => { console.error(`Fatal: ${e.message}`); process.exit(1); });
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "enrich-companies",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enrich a CSV of companies with revenue, credit score, employees, and financial data from 272M+ companies. Free tier: 50 lookups/month.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"enrich-companies": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"csv",
|
|
11
|
+
"enrichment",
|
|
12
|
+
"company data",
|
|
13
|
+
"business intelligence",
|
|
14
|
+
"lead enrichment",
|
|
15
|
+
"crm",
|
|
16
|
+
"revenue data",
|
|
17
|
+
"credit score",
|
|
18
|
+
"dun and bradstreet alternative",
|
|
19
|
+
"zoominfo alternative",
|
|
20
|
+
"clearbit alternative",
|
|
21
|
+
"data enrichment",
|
|
22
|
+
"b2b data",
|
|
23
|
+
"company lookup",
|
|
24
|
+
"sales intelligence"
|
|
25
|
+
],
|
|
26
|
+
"author": "SCALA AI <ale@get-scala.com>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"homepage": "https://score.get-scala.com/api",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/Alessandro114/enrich-csv"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=16"
|
|
35
|
+
}
|
|
36
|
+
}
|