
Enriching Tables
- 2 installs
- 4 repo stars
- Updated May 30, 2026
- brightdata/open-enrich
enriching-tables is a skill that uses the @brightdata/enrich CLI to add web-sourced columns (firmographics, tech stack, work emails, LinkedIn) to a CSV of companies or people.
About
enriching-tables uses the @brightdata/enrich CLI to turn a flat CSV of companies or people into an enriched one. Each row is researched on the live web through Bright Data plus an LLM agent swarm, and new columns like employee count, funding, tech stack, or work emails are appended from a plain-English description. A developer uses it to enrich leads, prospects, or a CRM export, running a dry-run for a cost estimate before approving the paid run.
- Enriches a CSV of companies or people with web-sourced columns (employee count, revenue, funding, tech stack, work email
- Uses the @brightdata/enrich CLI with a resumable, streaming, cost-reporting run
- Enforces a mandatory dry-run and user cost approval before any paid enrichment
Enriching Tables by the numbers
- 2 all-time installs (skills.sh)
- Ranked #730 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
enriching-tables capabilities & compatibility
Requires four API credentials; cost scales with rows x fields and is billed by Bright Data plus the LLM provider. A dry run estimates cost before spending.
- Capabilities
- data enrichment · lead enrichment · csv augmentation
- Works with
- linkedin · salesforce · openai · anthropic
- Use cases
- data analysis · research · marketing
- Runs
- Runs locally
- Pricing
- Bring your own API key
What enriching-tables says it does
Enrich any CSV of companies or people with web-sourced data (employee count, revenue, funding, tech stack, work emails, LinkedIn, and custom fields) using the @brightdata/enrich CLI.
The CLI needs **Node.js ≥ 20** and **four credentials**.
**Never kick off a paid run without a dry run first.** `--dry-run` resolves the full config — identifier column, the exact fields it will add, output path, and a cost estimate — and exits without touc
npx skills add https://github.com/brightdata/open-enrich --skill enriching-tablesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 4 |
| Last updated | May 30, 2026 |
| Repository | brightdata/open-enrich ↗ |
What it does
Enrich a CSV of companies or people with web-sourced columns like funding, tech stack, and work emails.
Who is it for?
Sales and growth developers enriching a CSV of leads, accounts, or contacts with web-sourced fields.
Skip if: One-off single-entity lookups or workflows where paid web enrichment is not acceptable.
When should I use this skill?
The user wants to enrich, augment, or add columns to a spreadsheet of companies, domains, or contacts.
What you get
An enriched CSV with appended web-sourced columns, produced from a resumable run after a cost-estimated dry run.
- An enriched CSV with appended web-sourced columns
- A dry-run cost estimate and field list
By the numbers
- 4 required credentials
- 4-step enrichment flow
Files
Enriching Tables with @brightdata/enrich
@brightdata/enrich (CLI command: enrich) turns a flat CSV into an enriched one. Each row is researched on the live web through Bright Data's infrastructure and an LLM agent swarm, and new columns are appended — company size, funding, tech stack, work emails, LinkedIn URLs, or anything you can describe in plain English. Every run is resumable, streams progress, and reports cost.
This skill takes a user from zero (no account) to a finished enriched CSV, and assumes nothing about prior Bright Data experience.
When you reach for this
Any time a user has a table and wants more columns sourced from the web: a list of company domains that needs employee counts and funding stages, a Salesforce export missing industries, a contact list that needs verified work emails, or a vague "can you find X for each of these." If they hand you a CSV and describe data they don't yet have, this is the tool.
The shape of the work
The flow is always the same four beats. Do them in order; don't skip the dry run.
Enrichment Progress:
- [ ] 1. Confirm prerequisites (Node ≥20, four credentials resolve)
- [ ] 2. Inspect the input CSV (identifier column, what's already filled)
- [ ] 3. Dry-run to lock config + estimate cost, then get the user's OK
- [ ] 4. Run the real enrichment, report output path + costCopy that checklist into your working notes and tick items off — it keeps you from spending the user's money before they've approved the plan.
Step 1 — Prerequisites
The CLI needs Node.js ≥ 20 and four credentials. If the user has never set this up, walk them through references/onboarding.md — it covers creating a Bright Data account, provisioning the two required zones, and getting an LLM key, with where-to-click pointers. Don't paraphrase those steps from memory; read that file and follow it.
The four credentials:
| Variable | What it is | Where it comes from |
|---|---|---|
BRIGHT_DATA_API_KEY | Bright Data account API token | Bright Data dashboard → Settings → API keys |
BRIGHT_DATA_SERP_ZONE | name of a SERP API zone | Bright Data dashboard → Proxies & Scraping → your SERP zone |
BRIGHT_DATA_UNLOCKER_ZONE | name of a Web Unlocker zone | Bright Data dashboard → Proxies & Scraping → your Unlocker zone |
OPENROUTER_API_KEY | LLM provider key | openrouter.ai/keys (or use OPENAI_API_KEY / ANTHROPIC_API_KEY instead) |
There are two ways to supply them. Environment variables (or a .env file in the working directory) always take precedence, which is what you want for scripted/non-interactive runs:
export BRIGHT_DATA_API_KEY=...
export BRIGHT_DATA_SERP_ZONE=...
export BRIGHT_DATA_UNLOCKER_ZONE=...
export OPENROUTER_API_KEY=...Or the user can store them once with the interactive setup (saved to a 0600 config file in their OS config dir):
npx @brightdata/enrich loginlogin validates each credential against the live API before saving — a wrong key fails fast here rather than mid-run. To wipe stored credentials, enrich logout.
Verify before doing anything else. Run the bundled preflight, which checks the Node version and confirms all four credentials resolve (via a dry run, so it spends nothing):
bash scripts/preflight.sh path/to/input.csvIf it reports a missing credential, send the user to references/onboarding.md for the one that's missing rather than guessing.
Step 2 — Inspect the CSV
Look at the file before configuring the run. You need to know two things:
- The identifier column — the column the agents use to find each entity on the web. A company domain or website is ideal; a company name works; for people, an email plus first/last name columns. The CLI auto-detects this, but eyeball it so you can override with
--columnif the guess would be wrong (e.g. a file with bothdomainandpersonal_domain). - What's already filled — there's no point (and no spend) re-fetching columns the file already has. Enrich the gaps.
A quick head of the file is enough. For the example file in this repo:
head -5 examples/companies.csv
# company,domain,hq_country
# Stripe,stripe.com,United States
# ...Here domain is the natural identifier.
Step 3 — Dry run, then get approval
Never kick off a paid run without a dry run first. --dry-run resolves the full config — identifier column, the exact fields it will add, output path, and a cost estimate — and exits without touching any API.
npx @brightdata/enrich examples/companies.csv \
--describe "employee count, total funding raised, primary tech stack" \
--dry-runYou'll get back something like the columns it will add, the detected identifier, and Estimated: ~$X.XX (Bright Data) + ~$Y.YY (LLM). Show the user this estimate and the field list, and wait for an explicit go-ahead before the real run — especially for files over a few dozen rows. Cost scales with rows × fields; a 5,000-row file is real money.
Decide how to specify fields (see the full catalog in references/fields.md):
--describe "<plain english>"— natural language. The CLI first tries to map your words onto preset fields; if it can't, an LLM designs custom fields from the description. Use this for anything bespoke ("their Series B lead investor", "whether they use Kubernetes").--fields a,b,c— exact preset field names, comma-separated. Faster and deterministic. Use when you know the names.--describeand--fieldsare mutually exclusive.
Step 4 — Run it
Once approved, drop --dry-run:
npx @brightdata/enrich examples/companies.csv \
--describe "employee count, total funding raised, primary tech stack"The enriched file is written to <input>.enriched.csv by default (override with -o). Progress streams to stderr with a live cost meter and ETA; the final summary prints rows done/skipped/errored, total cost split by LLM vs Bright Data, and the output path. Report that summary and the output path back to the user.
The options that matter most
| Option | Use it when | Default |
|---|---|---|
--describe "<text>" | You want fields in plain English (incl. custom) | — |
--fields a,b,c | You know the exact preset names | — |
--column <name> | Auto-detection picks the wrong identifier | auto |
--first-name <col> / --last-name <col> | Enriching people — lets agents disambiguate same-company contacts | — |
-o, --output <path> | You want a specific output filename | <input>.enriched.csv |
--concurrency <n> | Tune rows-in-flight (lower if rate-limited, higher to go faster) | 30 |
--dry-run | Always, before a real run | off |
--json | You need machine-readable NDJSON instead of a CSV (pipe to jq) | off |
--no-resume | You want to overwrite rather than continue a prior run | resume on |
-q / -v | Quiet (summary only) / verbose (every agent step) | normal |
Resuming is automatic
If a run is interrupted (Ctrl-C, crash, or you just stop it), re-run the exact same command and it picks up where it left off — it checks the existing output's header and skips rows already written. This only works if the field set matches, so keep --describe/--fields identical. To start fresh instead, pass --no-resume or delete the output file. This makes large files safe to run in chunks.
Enriching people, not just companies
For contact-level data (work email, LinkedIn, title, seniority), point the CLI at the name columns so it can tell two people at the same company apart:
npx @brightdata/enrich contacts.csv \
--first-name first_name --last-name last_name --column work_email \
--fields person_email,person_linkedin_url,person_title,person_seniorityCost — set expectations honestly
Bright Data bills roughly $0.0015 per tool call; a typical row touches 5–15 calls depending on how much you ask for. LLM cost is separate and paid to your provider. As a rule of thumb a 5-field CRM fill is well under a cent of Bright Data spend per row; deep person+company enrichment runs a few cents. The --dry-run estimate is your source of truth — always surface it. Compared to commercial enrichment vendors at $0.50–$2.00/row, this is one to two orders of magnitude cheaper, but it is not free, so confirm before large runs.
When something goes wrong
For credential errors, header-mismatch on resume, empty-field results, rate limits, and other failure modes, see references/troubleshooting.md — match the error message to its row and follow the fix. Don't retry a failing command unchanged; diagnose first.
Field catalog
The 36 built-in preset fields and 6 quick-start bundles (Startup Prospecting, Enterprise Research, Quick CRM Fill, Competitor Analysis, Person Lookup, Full Company + Person) are listed with descriptions in references/fields.md. Read it when the user wants specific named fields or you need to translate a request into the right --fields list.
Installing / publishing this skill
This skill lives at skills/enriching-tables/SKILL.md in the brightdata/open-enrich repo — the layout skills.sh indexes automatically — so users install it with npx skills add brightdata/open-enrich/skills/enriching-tables. Listing details and the README badge are documented in the repo's main README ("Or drive it from your coding agent"); publishing is a one-time author task, not part of running an enrichment.
Field catalog
The 36 preset fields you can pass to --fields (comma-separated, exact names), grouped by category. With --describe "<text>" you don't need exact names — the CLI maps your words onto these presets, and invents custom fields when nothing matches. Use --fields when you want determinism; use --describe for anything bespoke.
If you pass a name that isn't in this list, the CLI errors and prints the valid names — so copy names from here exactly.
Contents
- ICP qualification (company size/segmentation)
- Buying signals (timing)
- Personalization (outreach hooks)
- Contact data (people + company contacts)
- Social & web (links)
- Quick-start bundles
ICP qualification
| Field name | What it returns |
|---|---|
employee_count | Headcount (number) — key size filter |
revenue | Revenue / budget signal |
industry | Primary industry / vertical |
headquarters | HQ location |
company_type | Startup / SMB / enterprise |
funding_stage | Seed, Series A/B/C, public, etc. |
founded_year | Year founded (number) |
Buying signals
| Field name | What it returns |
|---|---|
recent_funding | Recent raise (amount/date) |
hiring_velocity | How fast they're hiring |
leadership_changes | New VPs/CxOs |
job_postings | Open roles (array) |
tech_changes | Tools they're switching to/from |
Personalization
| Field name | What it returns |
|---|---|
description | Company's own description |
tech_stack | Tools/technologies they use (array) |
company_mission | Stated mission |
recent_news | Recent news item |
Contact data
Company-level contacts and person-level fields. The person_* fields work best when you point --first-name/--last-name (and an identifier like an email) at the right columns so the agents can disambiguate people.
| Field name | What it returns |
|---|---|
ceo_name | CEO name |
founders | Founder names (array) |
email_pattern | Email format, e.g. first@company.com |
person_email | Work email for the contact (SERP + LinkedIn/GitHub fallback) |
person_phone | Direct phone (only if explicitly found) |
person_linkedin_url | LinkedIn profile URL |
person_github_url | GitHub profile URL |
person_twitter_url | Twitter/X profile URL |
person_title | Current job title |
person_seniority | C-Suite / VP / Director / Manager / IC |
person_location | City/region |
person_headline | LinkedIn headline |
person_summary | Short professional bio |
Social & web
| Field name | What it returns |
|---|---|
linkedin_url | Company LinkedIn page |
twitter_url | Company Twitter/X |
github_url | Company GitHub org |
blog_url | Company blog |
website | Company website |
Quick-start bundles
These are named field sets defined by the engine. There's no single CLI flag that takes a bundle id, so to use one, pass its member fields to --fields. The bundles (and their intent) are:
| Bundle | Member fields |
|---|---|
| Quick CRM Fill | industry,employee_count,revenue,headquarters,website |
| Startup Prospecting | employee_count,revenue,industry,headquarters,company_type,funding_stage,recent_funding,hiring_velocity,leadership_changes,job_postings,description,tech_stack,company_mission,recent_news |
| Enterprise Research | employee_count,revenue,industry,headquarters,company_type,funding_stage,founded_year,ceo_name,founders,tech_stack,description |
| Competitor Analysis | tech_stack,employee_count,revenue,funding_stage,recent_news |
| Person Lookup | person_email,person_linkedin_url,person_github_url,person_title,person_seniority,person_location |
Example — Quick CRM Fill:
npx @brightdata/enrich leads.csv --fields industry,employee_count,revenue,headquarters,websiteNote: the engine also defines a "Full Company + Person" bundle that lists acompany_namekey, which is not a standalone preset field. Prefer the explicit member lists above, or just describe what you want with--describeand let the CLI resolve it.
First-time onboarding
For a user who has never used Bright Data or @brightdata/enrich. Walk through these in order. The whole thing is ~10 minutes; the only thing that can stall is Bright Data KYC on a fresh account.
Contents
- 1. Install Node.js (≥ 20)
- 2. Create a Bright Data account
- 3. Provision the two zones (SERP API + Web Unlocker)
- 4. Get the Bright Data API key
- 5. Get an LLM key (OpenRouter, or OpenAI/Anthropic)
- 6. Store credentials with
enrich login - 7. Verify
1. Install Node.js (≥ 20)
The CLI runs on Node 20 or newer. Check:
node -vIf it's missing or below v20, install the current LTS from https://nodejs.org (or via nvm install 20). No global install of the CLI is needed — npx @brightdata/enrich fetches it on demand. To pin it, npm install -g @brightdata/enrich.
2. Create a Bright Data account
Sign up at https://brightdata.com. New accounts may need a quick KYC/verification step before zones can be created — if zone creation is blocked, that's usually why; complete verification first.
3. Provision the two zones
The engine uses two distinct Bright Data products, each exposed as a zone. In the dashboard, go to Proxies & Scraping (the product catalog) and create one of each if you don't have them:
- SERP API zone — runs the web searches that find each entity. Note the zone name you give it; that string is
BRIGHT_DATA_SERP_ZONE. - Web Unlocker zone — fetches and unblocks the pages found. Its zone name is
BRIGHT_DATA_UNLOCKER_ZONE.
The values are the names of the zones (e.g. serp_api1, unblocker1), not URLs or passwords.
4. Get the Bright Data API key
Dashboard → Settings → API keys → create/copy a key (https://brightdata.com/cp/setting/users). This is BRIGHT_DATA_API_KEY. It's an account-wide token — keep it secret.
5. Get an LLM key
The agents need an LLM. Default is OpenRouter (model-agnostic, one key):
- Create a key at https://openrouter.ai/keys → that's
OPENROUTER_API_KEY.
You can use OpenAI or Anthropic instead — set OPENAI_API_KEY or ANTHROPIC_API_KEY and the engine picks the right adapter. Add a few dollars of credit at your provider; LLM spend per row is fractions of a cent but a depleted balance will fail the run.
6. Store the credentials
Two options. Environment variables / `.env` take precedence and are best for scripts:
export BRIGHT_DATA_API_KEY=...
export BRIGHT_DATA_SERP_ZONE=... # the SERP zone *name*
export BRIGHT_DATA_UNLOCKER_ZONE=... # the Web Unlocker zone *name*
export OPENROUTER_API_KEY=...Or store them once, interactively — login validates each against the live API before saving and writes a 0600 config file in your OS config dir:
npx @brightdata/enrich loginIt skips any field already set via an env var. To remove the stored config later: enrich logout.
7. Verify
From a directory with a CSV (the repo's examples/companies.csv works), run the preflight — it checks Node and confirms all four credentials resolve via a no-spend dry run:
bash scripts/preflight.sh examples/companies.csvA passing run ends with ✓ Preflight passed. If a credential shows ✗, that variable didn't resolve from env, .env, or the stored config — set it and re-run. You're now ready for Step 2 in SKILL.md (inspect the CSV).
Troubleshooting
Match the symptom to its fix. Don't re-run a failing command unchanged — diagnose first. Exit codes: 2 = bad usage/config (your fault, free to fix), 1 = runtime error, 130 = interrupted (resume it).
"Error: missing credentials" (exit 2)
One or more of the four credentials didn't resolve from env vars, a .env in the working directory, or the stored login config. The error prints a ✓/✗ line per variable — fix the ✗ ones.
- Set the missing env var, or run
enrich loginand fill it in. - Remember resolution order: real env vars >
.env> stored config. If a stale env var is overriding your stored config, unset it. - First-time setup:
references/onboarding.md.
"invalid API key" during enrich login
login validates live before saving. A ✗ Bright Data: invalid API key or ✗ OpenRouter: invalid API key means the key is wrong or revoked — regenerate it in the respective dashboard and re-enter. Nothing is saved until both validate.
"could not auto-detect identifier column" (exit 1)
The CLI couldn't decide which column identifies each row. Tell it explicitly:
--column <the_column_with_domains_or_emails>A domain/website column auto-detects most reliably; a free-text name column is hardest.
"unknown field names: ..." (exit 2)
A name in --fields isn't a preset. The error lists the valid names; copy from references/fields.md exactly, or switch to --describe "<plain english>" which doesn't require exact names.
"Error: --describe and --fields are mutually exclusive" (exit 2)
Pass one or the other, never both.
"exists with incompatible columns" / header mismatch (exit 1)
The output file already exists but its header doesn't match the columns this run would produce — usually because you changed --describe/--fields between runs, so resume can't safely continue.
- To continue the previous run: use the same
--describe/--fieldsas before. - To start over: pass
--no-resume(overwrites) or pick a new-ooutput path, or delete the old file.
A run was interrupted (Ctrl-C, crash) — exit 130
Just re-run the exact same command. Resume is automatic: it reads the existing output's header and skips rows already written. This only works if the field set is identical, so don't change --describe/--fields.
Cells come back empty
Empty values are normal — they mean the agents couldn't find that fact for that entity (e.g. a private company's revenue, or a person's direct phone, which is only filled when explicitly found). It is not an error. To improve hit rate:
- Give a better identifier: a domain beats a company name; for people, supply
--first-name/--last-name. - Make sure the entity is actually findable on the public web.
Rate limiting / slow / flaky
- Lower
--concurrency(default 30) to ease pressure:--concurrency 5. - Check your Bright Data zone status and account budget in the dashboard.
- For very large files, run in chunks — stop and re-run (resume picks up).
"no fields resolved from your description" (exit 1)
The --describe text was too vague to map or generate fields from. Be more concrete ("employee count, funding stage, CEO name") or switch to explicit --fields.
Noisy stderr during a run (warnings, "Snapshot not ready", MaxListeners)
Lines like [crunchbase] Snapshot not ready (attempt N/60), unexpected API response shape, or a Node MaxListenersExceededWarning are the engine's internal progress/diagnostic chatter on stderr. They are not failures — a row only failed if its final result line says ✗ error. The authoritative outcome is the closing summary (N done • N skipped • N errors) and the written CSV. A scraper source (e.g. Crunchbase) can time out on one source while the row still completes from others. Use -q to suppress progress and see only the summary.
Debugging what the agents are doing
Add -v (verbose) to see every agent step per row, or --json to stream raw NDJSON events you can inspect with jq. Use --dry-run to confirm the resolved config without spending anything.
#!/usr/bin/env bash
# Preflight check for @brightdata/enrich.
# Verifies Node >= 20 and that all four credentials resolve — without spending anything.
# Usage: bash scripts/preflight.sh path/to/input.csv
#
# The credential check works by running `enrich ... --dry-run`, which resolves config
# (env > .env > stored login config) and exits before making any API call. Exit 0 means
# you're clear to run a real enrichment; any non-zero exit prints what to fix.
set -u
CSV="${1:-}"
if [ -z "$CSV" ]; then
echo "usage: bash scripts/preflight.sh path/to/input.csv" >&2
exit 2
fi
if [ ! -f "$CSV" ]; then
echo "✗ input CSV not found: $CSV" >&2
exit 2
fi
fail=0
# --- Node >= 20 -------------------------------------------------------------
if ! command -v node >/dev/null 2>&1; then
echo "✗ Node.js not found. Install Node >= 20: https://nodejs.org" >&2
fail=1
else
major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
if [ "$major" -lt 20 ]; then
echo "✗ Node $(node -v) is too old; @brightdata/enrich needs Node >= 20." >&2
fail=1
else
echo "✓ Node $(node -v)"
fi
fi
[ "$fail" -ne 0 ] && exit 1
# --- Credentials, via a dry run (no API spend) ------------------------------
# --describe is required by the CLI; the value is irrelevant for a dry run.
echo "• Resolving credentials and config (dry run, no spend)..."
out="$(npx --yes @brightdata/enrich "$CSV" --describe "industry" --dry-run 2>&1)"
code=$?
if [ "$code" -eq 0 ]; then
echo "$out"
echo "✓ Preflight passed — credentials resolve and config is valid. Clear to run a real enrichment."
exit 0
fi
echo "$out" >&2
if echo "$out" | grep -qi "missing credentials"; then
echo "" >&2
echo "✗ One or more credentials are missing (see ✗ marks above)." >&2
echo " Fix: run \`enrich login\`, or export the missing vars, then re-run preflight." >&2
echo " First-time setup walkthrough: references/onboarding.md" >&2
else
echo "" >&2
echo "✗ Preflight failed (exit $code). See message above; references/troubleshooting.md may help." >&2
fi
exit 1
Related skills
FAQ
What credentials does it need?
Node.js >= 20 and four credentials: BRIGHT_DATA_API_KEY, a SERP zone, an Unlocker zone, and an OPENROUTER_API_KEY (or OPENAI/ANTHROPIC key).
Can I preview cost before spending?
Yes. A mandatory --dry-run resolves the config and returns a cost estimate without touching any API; you approve before the paid run.