Coding

FlashRev AI Enrich

Try it

Enrich CSV lead lists through FlashRev's schema-first CLI with dry-run validation and sample preview.

What it does

Drive an AI agent through the flashrev-ai-enrich npm CLI to fill missing company or person fields, verify or unlock emails and phones, find executives or LinkedIn posts, match FlashRev IDs, run Google search/news/maps lookups, scrape a page, or apply an LLM per row. The agent sets FLASHREV_ENRICH_AI_MODE=1, fetches capabilities from a live schema --json, builds explicit --capability, --map, and --output flags per command, and always validates with dry-run plus a 10-row sample preview before any live batch. Approval is required at overwrite, high-volume, contact-unlock, customer_api egress, and --allow-internal-targets boundaries.

When to use it

  • Verifying business emails across a CSV lead list
  • Unlocking phone numbers and contact fields for people rows
  • Filling missing company data from name plus domain or LinkedIn
  • Scraping a page or running an LLM over each CSV row

The skill document

FlashRev AI Enrich

Use the flashrev-ai-enrich CLI to enrich CSV lead lists through FlashRev. The CLI does not send outreach messages. It reads CSV files, maps CSV columns to FlashRev capability inputs, validates the job with dry-run, previews enriched sample rows, then writes an enriched CSV.

Commands

flashrev-ai-enrich init [--force]                              Write default config
flashrev-ai-enrich doctor [--no-api]                            Self-check Node / config / API
flashrev-ai-enrich tokens [--json]                              Show balance / total / used / plan
flashrev-ai-enrich token-history [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N] [--json]
                                                                Show consumption log (auto-paginates)
flashrev-ai-enrich schema [--advanced|--all] [--json]           List production-backed capabilities (synced from backend at runtime).
                                                                Default view shows recommended product entries and category summaries;
                                                                --advanced lists all atoms by category, --all shows the raw registry
flashrev-ai-enrich plan --source X.csv [--goal contact|person|company|identity|all] [--contact-type email|phone|both] [--json] [--emit-jobs|--no-emit-jobs]
                                                                Recommend concrete follow-up capabilities from the CSV's non-empty
                                                                columns; JSON is read-only unless --emit-jobs is passed. 0 tokens
flashrev-ai-enrich dry-run  --source leads.csv (--capability ID | --job planned.job.json | --prompt "...") [--map ...] [--output ...] [--json]
                                                                Validate job and show run plan without calling backend. --json returns approvalReasons/wouldOverwrite
flashrev-ai-enrich run      --source leads.csv [--out X.csv] (--capability ID | --job F | --prompt "...") [--yes] [--concurrency N] [--sample-size N] [--sample-only] [--report-json [PATH]] [--guided] [--overwrite]
                                                                Real enrichment with sample preview. In AI mode stdout is JSON and progress is stderr

Agent Execution Contract

Agent runtimes should treat this CLI as a schema-first structured tool, not a natural-language router.

Use this order for every non-trivial enrichment job:

  1. Set FLASHREV_ENRICH_AI_MODE=1.
  2. Run flashrev-ai-enrich doctor --no-api.
  3. Run flashrev-ai-enrich schema --json.
  4. Select one or more funcName values returned by the live schema. Each CLI command still runs exactly one --capability. For broad requests, prefer flashrev-ai-enrich plan --source X.csv --json (0 tokens): it returns ranked follow-up jobs with inferred --map values, argv, approvalReasons[], output paths, and overwrite/high-volume boundaries — no need to parse human recommendation text. Add --emit-jobs only after file creation is acceptable.
  5. Run flashrev-ai-enrich tokens --json.
  6. Build explicit --map and --output flags from the CSV headers and the selected schema for each selected capability, or use job files emitted by plan --emit-jobs.
  7. Run flashrev-ai-enrich dry-run for each selected capability before that capability's live run (--json is automatic in ai-mode). Read approvalReasons[], requiresApproval, and wouldOverwrite.
  8. Ask for approval unless the user already authorized --yes. Always stop for approval at the boundaries flagged by plan --json or dry-run --json (approvalReasons[] — contact cleartext unlock, customer_api egress, --allow-internal-targets, overwriting existing files, high-volume runs).
  9. For agent-mediated approval, first run flashrev-ai-enrich run --sample-only --sample-size 10 ... --yes after the user approves spending sample tokens. Show the JSON sample to the user and continue with the full run only after approval.
  10. Run flashrev-ai-enrich run, chaining each capability from the previous output CSV when multiple capabilities are needed. In AI mode, stdout is the structured result (output path, status counts, exact token spend); use --report-json path when a file copy is needed.
  11. Report the final output path, per-capability status counts, row errors, and actual token spend from the run report / CLI summary and token history.

Rules for agents:

  • Do not use --prompt by default. Use it only when the user explicitly requests prompt routing or does not want to choose a capability.
  • Never invent capability IDs, input fields, or output fields. Use only the live schema --json response.
  • Prefer explicit --capability ID even when the user's request is written in natural language.
  • If a requested capability is missing from the live schema, say it is unavailable in the connected environment instead of guessing a hidden backend route.
  • For a broad request such as "enrich this CSV" or "complete these people", enrich both person profile fields and contact fields when the CSV has a person identifier.
  • For contact-only planning, use plan --contact-type email|phone|both when the user asks for one contact channel or both.
  • If the request needs customer_api, confirm the destination domain before live run.
  • If the request needs --allow-internal-targets, get separate explicit approval before using that flag.

Supported starting signals

Use this section to decide whether the user's CSV has enough information to start enrichment. Treat it as product-level guidance; the live schema --json response remains authoritative for exact capability inputs, rules, and output fields.

Person enrich can start from:

Signal strengthAccepted inputs
Bestflashrev_person_id, person_linkedin_url, linkedin_url
Strongemail, phone
Commonfull_name + company_name, first_name + last_name + company_name
Better commonName plus company plus job_title
Company contextName plus company_website, company_domain, or company_linkedin
Lowest confidencecompany_name + job_title; use top-one selection only after the user accepts ambiguity, then pass --param allow_low_confidence_match=true

Company enrich can start from:

Signal strengthAccepted inputs
Bestflashrev_company_id, company_website, company_domain, company_linkedin
Commoncompany_name, preferably with company_country, company_city, or industry when available

Contact enrich can start from:

Signal strengthAccepted inputs
Bestflashrev_person_id, person_linkedin_url, linkedin_url
Strongemail, phone
Commonfull_name + company_name, first_name + last_name + company_name, or name plus company website/domain/LinkedIn
Role searchcompany_name + job_title; use only after the user accepts top-one selection, then pass --param allow_low_confidence_match=true

Rules:

  • Do not require normal users to provide FlashRev IDs; prefer LinkedIn, email, phone, or name plus company when IDs are absent.
  • Treat email, phone, LinkedIn, and name plus company as identity signals for broad person enrichment, not as proof that the user only wants contact fields.
  • If the user explicitly asks for contact-only output, route to the contact capabilities instead of profile enrichment.
  • If no supported starting signal is present, ask for LinkedIn URL, email, phone, name plus company, or company plus job title before running enrichment.

Required confirmations before real run

  1. User has a FlashRev account with available tokens (flashrev-ai-enrich tokensremaining > 0).
  2. FLASHREV_API_KEY env var is set (generated from https://info.flashlabs.ai/settings/privateApps).
  3. Source CSV path and output CSV path are confirmed.
  4. --capability ID from live flashrev-ai-enrich schema --json is confirmed. Use --prompt "" only when the user explicitly asks for prompt routing.
  5. Input mappings (--map flashrev_field=csv_column) cover at least one capability rule. Skipped only when --prompt is explicitly used and the LLM returns valid mappings (still subject to rule validation afterwards).
  6. Output mappings (--output csv_col=response_field) or --output-fields are confirmed. Skipped only when --prompt is explicitly used and the LLM returned mappings, but always required for dynamic-output capabilities (e.g., run_llm, scrape_and_extract).
  7. dry-run --json first to validate mappings, row count, planned API calls, effective concurrency, approvalReasons[], and wouldOverwrite.
  8. For agent workflows, prefer run --sample-only --json --yes after the user approves sample-token spend; show the returned sample JSON and continue to the full run only after approval.
  9. Do not proceed past the sample preview (default 10 rows, configurable via --sample-size N) unless the user approves or --yes is set.
  10. Existing output files require explicit approval and --overwrite.
  11. customer_api and --allow-internal-targets each require separate explicit approval.

Input modes

A. CSV mode (typical batch)

flashrev-ai-enrich run \
  --source leads.csv --out leads.enriched.csv \
  --capability enrich_email \
  --map first_name=first_name --map last_name=last_name --map company_name=company \
  --output verified_email=verified_business_email \
  --yes

--map connects CSV column → capability input field; --output connects CSV output column → backend response field.

B. Inline mode (single row test, no CSV)

flashrev-ai-enrich run \
  --capability verify_email \
  --input email=ada@example.com \
  --output ok=deliverable_email \
  --out out.csv --yes

In inline mode the --input key=value pairs are auto-mapped (no need for --map).

C. Job file (for repeatable presets)

flashrev-ai-enrich run --source leads.csv --out out.csv --job enrich.job.json --yes

Job file shape:

{
  "capability": "enrich_email",
  "inputMapping": {
    "first_name":  "first_name",
    "last_name":   "last_name",
    "company_name": "company"
  },
  "outputs": {
    "verified_business_email":  "verified_business_email",
    "all_verified_business_emails": "all_verified_business_emails"
  }
}

Default person enrichment routing

When the user asks broadly to "enrich" a people CSV, do not stop at contact lookup. Treat the default outcome as person profile plus contact data when the CSV has a usable person identifier.

Preferred broad-person flow (plan-driven):

  1. If the live schema exposes enrich_person, run it first — it resolves identity from any supported signal (phone, email, LinkedIn, name + company) and returns a People-detail-aligned full profile: stable anchors (person_id, person_linkedin_url), current role and company, location, skills, work/education history, plus contact data (business_emails / personal_emails / phones, unlock_status / unlock_types, contact_billing_status). enrich_person unlocks email/phone contacts BY DEFAULT and this can spend contact credits (Credit orgs) or tokens (Token orgs) — always dry-run and sample-preview before a live batch. Treat phone/email/LinkedIn as identity signals for this step, not as contact-only requests. If only enrich_person_basic is exposed, use that instead; if neither exists yet, run the fallback pipeline from the table below.
  2. Run flashrev-ai-enrich plan --source .csv --json and execute the planned jobs the user approves (dry-run --job then run --sample-only --job, then full run --job). Stop for approval on every requiresApproval: true entry and inspect approvalReasons[].

Contact-only requests can use plan --contact-type email|phone|both --json or route directly to enrich_email / enrich_phone / enrich_contact_waterfall.

enrich_person contact-unlock rules:

  • Contacts are ON by default. There is no agent-facing include_contacts input — do not invent one. Profile-only mode is controlled by backend Nacos configuration (flashrev.open-enrich.person.include-contacts-default / -company-overrides); if the user insists on "profile only, no contacts", explain that this is an ops-side backend switch and only run if the environment is already configured profile-only (rows then report contact_billing_status=disabled).
  • To narrow contact types while contacts are enabled, pass a static param instead of a fake CSV column: --param requested_types=email or --param requested_types=phone (blank/omitted = both).
  • company_name/company_website/company_linkedin + job_title is low-confidence top-candidate matching. Do not run it automatically. First ask the user to confirm they accept top1 person selection and possible contact unlock cost; only then pass --param allow_low_confidence_match=true.
  • contact_billing_status per row: ok (all requested types billed or cached), partial_billing_blocked / billing_blocked (insufficient credits/tokens — cleartext is never returned for a blocked type), disabled (Nacos profile-only mode).

Fallback decision table (also used when the user names the outcome explicitly):

User intentDefault capabilities
Broad person enrich, complete people, enrich this CSVenrich_person when live schema exposes it; otherwise enrich_contact_waterfall + get_person_job_title + get_person_current_company + get_person_location
Contact info, emails and phonesenrich_contact_waterfall only
Emails only (contact-only)enrich_email only
Phones or mobiles only (contact-only)enrich_phone only
Full profile plus one contact typeenrich_person with --param requested_types=email (or phone)
Profile only, no contacts requestedenrich_person — but profile-only mode is a backend Nacos switch, not a request flag; see the contact-unlock rules above

Apply these rules:

  • Use only capabilities exposed by the live schema --json response. If one capability in the default pipeline is missing, skip that step and report it.
  • Prefer person_linkedin_url / linkedin_url inputs when the CSV has a LinkedIn column. Otherwise use the strongest identity group available from the live schema.
  • Chain outputs through intermediate CSV files and make the final file the last step's --out.
  • Preserve earlier run results by giving each follow-up run distinct status and error columns, for example job_title_enrich_status, current_company_enrich_status, and location_enrich_status.
  • Keep flashrev_enrich_status for the first/main contact run unless the user asks for custom status columns.

Contact enrichment routing

Use explicit contact capabilities from the live schema:

User intentCapability
Emails onlyenrich_email
Phones or mobiles onlyenrich_phone
Emails and phonesenrich_contact_waterfall, only when live schema exposes it
Emails and phones, no waterfall in schemaAsk before running enrich_email and enrich_phone sequentially

enrich_email and enrich_phone are still valid single-type contact capabilities. They use the backend contact lookup path for the requested contact type, so agents should keep using them for single-type requests instead of forcing enrich_contact_waterfall.

Do not require users to provide flashrev_person_id; normal users usually do not have it. Use the strongest available identity input:

Input strengthAccepted inputs
Bestperson_linkedin_url
Strongemail, phone, flashrev_person_id
Commonfull_name + company_name, first_name + last_name + company_name
Better commonName plus company plus job_title
Company role searchcompany_name + job_title, optionally with company_website or company_linkedin; requires user confirmation and --param allow_low_confidence_match=true
Explicit opt-in onlyjob_title only; use only if the live schema exposes an explicit low-confidence opt-in field and the user accepts top-one selection

If none of those identity inputs are available, ask the user for a LinkedIn URL, email, phone, name plus company, or company plus job title before running contact enrichment.

D. Prompt routing mode (ad-hoc human use; costs 1 extra token)

Skip --capability and describe the intent in natural language. The CLI sends the prompt + CSV columns + capability registry to run_llm, which returns JSON { funcName, inputMapping, outputMapping, reasoning }; the CLI prints a Routing-decision block and then runs the resulting job through the normal dry-run / sample / run pipeline.

flashrev-ai-enrich run --source leads.csv --out leads.enriched.csv \
  --prompt "for each row, take the email column and verify it is a deliverable business email" \
  --yes

Rules of thumb when writing prompts:

  • Name the CSV column explicitly ("take the email column"); vague prompts make the LLM return empty mappings.
  • Describe the business outcome, not the capability name ("find the CEO" beats "use get_company_ceo").
  • One capability per prompt — the LLM picks exactly one funcName.
  • --map / --output on the command line override the LLM's choices; use them to lock specific columns while letting the LLM pick the capability.
  • --capability X --prompt "..." together: --capability wins, --prompt is ignored with a stderr warning (no routing token charged).
  • Unroutable prompts (e.g., "make me a sandwich") exit non-zero with the LLM's reasoning printed; zero rows run.

Agents must skip prompt routing unless the user explicitly requests it. schema --json plus explicit --capability ID is cheaper, faster, and deterministic.

For contact lookup, agents should use the contact routing table above and pass explicit --capability, never prompt routing.

Status semantics (output CSV columns)

Every output CSV gets flashrev_enrich_status and flashrev_enrich_error columns:

statusmeaning
successGot business data; charged per capability unitPriceToken.
cachedHit contact-unlock dedup (same person/contact type already unlocked). 0 tokens.
no_dataBackend returned 200 but the requested output fields are empty / null. 0 tokens.
failedHTTP error from backend, retries exhausted. 0 tokens.

Failed count > 0 with Tokens used > 0 means some rows got SOMETHING from backend (charged) but not the specific output fields the user asked for.

Cost reporting

Summary line in run output prints (balance before → balance after) — that delta is the authoritative amount charged for the row enrichments. Each row's individual cost.tokens reported by backend may be slightly off under high concurrency (known limitation; token-history is always exact).

Credit-based organizations: enrich_person / contact waterfall unlocks charge email/phone credits instead of tokens. The run summary prints a Credits used line (and --report-json includes creditsUsed) whenever credits were spent; per-row details come back in cost.credits / cost.emailCredits / cost.phoneCredits.

When --prompt is used, the Routing-decision block prints its own routing cost: 1 token(s) line. That 1 token is not included in the Summary balance before → after delta, since routing happens before the balance snapshot. Use token-history for the authoritative total after the run.

Special capability: customer_api

customer_api does NOT call FlashRev backend — the CLI fetches the user-provided URL locally and parses the response. 0 tokens.

Inputs (via --map = or --input =):

fieldrequireddefaultnotes
urlyestarget URL (alias: endpoint)
methodnoGETHTTP method
headersno{}JSON object of HTTP headers
bodynostring (sent as-is) or object (JSON-serialized; Content-Type defaults to application/json)
paramsnoobject of query-string params; appended to url
timeoutno30000milliseconds before AbortError

The response JSON (or { text } wrapper for non-JSON) becomes the row's enrichment data; map output columns via --output csv_col=response_field as usual. Useful for mixing 3rd-party APIs into the same enrichment workflow.

Security warnings (read before using customer_api in an agent context)

customer_api lets the CLI send arbitrary HTTP requests with row-derived URL / headers / body. The target URL is not owned by FlashRev — it is whatever the user, prompt, or CSV column supplied. This creates two real risk surfaces an agent must mitigate:

  1. SSRF / internal-network probing. A URL such as http://127.0.0.1:8500/, http://169.254.169.254/latest/meta-data/iam/security-credentials/ (AWS/GCP/Azure cloud-metadata), or any RFC1918 address (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) can be used to reach internal services or exfiltrate cloud IAM credentials. The CLI rejects these targets by default (HTTP 403 customer_api refuses internal / private target host) along with localhost, IPv6 loopback ::1, link-local fe80::/10, ULA fc00::/7, and non-http(s) schemes (file://, gopher://, data:, javascript:). Pass --allow-internal-targets only for deliberate local testing on a trusted machine.
  2. Lead-data exfiltration to user-controlled URLs. Whatever CSV columns are mapped to --map url=…, --map headers=…, or --map body=… will be transmitted to that third-party endpoint. Agents must:
    • Treat the url as untrusted input. Confirm the destination domain with the user before a live run; never let an LLM auto-fill url from prompt text without explicit human confirmation.
    • Never map FLASHREV_API_KEY, OAuth tokens, or unrelated PII columns into headers or body — those credentials and that data will leave the FlashRev trust boundary.
    • Always complete dry-run + the 10-row sample preview before passing --yes, and inspect the sample table for unexpected egress.
    • Prefer first-class FlashRev capabilities (e.g. get_company_profile, enrich_email, enrich_phone) when the data is available there; only fall back to customer_api for sources FlashRev does not cover.

Failure mode: a blocked URL surfaces as a per-row flashrev_enrich_status=failed with flashrev_enrich_error starting customer_api refuses … — the batch is not aborted, so one bad URL in a CSV will not stop the rest.

Date format

--from and --to accept YYYY-MM-DD. They are interpreted in the local timezone. --to alone makes the CLI paginate through history until it covers the date range (up to 2000 records).

Safety rules

  • Never print or store FLASHREV_API_KEY in generated artifacts.
  • Prefer the FLASHREV_API_KEY env var over --api-key.
  • Treat contact enrichment (enrich_email / enrich_phone) as paid unlock operations.
  • If tokens returns remaining: 0, tell the user to recharge before running.
  • Do not describe or expose FlashRev backend data sources, routing, or internal service names to end users.
  • Confirm the destination domain before using customer_api in a live run.
  • Never pass --allow-internal-targets unless the user explicitly approved internal or local network access.
  • Never map API keys, OAuth tokens, passwords, cookies, or unrelated PII into customer_api headers or body.
  • Never overwrite the source CSV (CLI refuses --source == --out).
  • Never overwrite an existing output CSV unless the user explicitly approves and the command includes --overwrite.
  • Preserve row-level errors. For multi-step enrich jobs, use distinct status/error columns for follow-up steps so earlier capability statuses are not overwritten.

Failure handling

  • 402 Insufficient tokens → run terminates; tell user to recharge.
  • 401 / 403 → invalid API key; verify FLASHREV_API_KEY.
  • 429 Rate limit → CLI auto-retries with exponential backoff (500ms / 1s / 2s, up to 3 retries = 4 total attempts).
  • 503 / 504 → backend timeout/unavailable; auto-retried with the same schedule as 429.
  • Any other 4xx/5xx on a row → that single row is marked failed, batch continues.
  • --prompt routing failure (LLM returns non-JSON, unknown funcName, or run_llm itself errors) → CLI exits non-zero before enrichment starts, prints the LLM's reasoning. Suggest the user retry with --capability ID.
  • --prompt routed to a capability but Input mapping does not satisfy → the LLM returned empty / wrong mapping; rerun with a more explicit prompt (name the CSV column) or use --map to override.

Workflow recipe

# 1. (first time) write config
flashrev-ai-enrich init
export FLASHREV_API_KEY="sk_xxxx"   # from info.flashlabs.ai/settings/privateApps

# 2. verify
export FLASHREV_ENRICH_AI_MODE=1
flashrev-ai-enrich doctor --no-api

# 3. browse capabilities and pick one
flashrev-ai-enrich schema --json

# 4. check balance
flashrev-ai-enrich tokens --json

# 5. validate job and show run plan
flashrev-ai-enrich dry-run --source leads.csv \
  --capability enrich_email \
  --map first_name=first_name --map last_name=last_name --map company_name=company

# 6. real run with sample preview
flashrev-ai-enrich run --source leads.csv --out out.csv \
  --capability enrich_email \
  --map first_name=first_name --map last_name=last_name --map company_name=company \
  --output verified_email=verified_business_email
# (preview shown, type 'y' to continue, or pass --yes to auto-confirm)

# 7. audit spend
flashrev-ai-enrich token-history --from 2026-05-01

Shortcut for ad-hoc human use (prompt routing)

When the user does not know the capability name and is willing to spend 1 extra token to let the LLM pick:

# dry-run only routes (1 token) — no enrichment
flashrev-ai-enrich dry-run --source leads.csv \
  --prompt "find the CEO of each company"

# real run: 1 routing token + N rows
flashrev-ai-enrich run --source leads.csv --out out.csv \
  --prompt "find the CEO of each company" --yes

Agents should skip this and pass --capability directly.

Questions people ask

Does the skill send outreach emails or messages?
No. The CLI only enriches CSVs and writes an output file; it does not send outreach messages of any kind.
Why is dry-run plus a sample preview required before a real run?
Because enrich_person and the contact capabilities can spend credits or tokens on unlock, and approval must be gathered separately at overwrite, high-volume, contact cleartext unlock, customer_api egress, and --allow-internal-targets boundaries.
What should be done when a capability is missing from the live schema --json?
Report that it is unavailable in the connected environment instead of guessing a hidden backend route. Capability IDs, input fields, and output fields must never be invented.

Related skills

Use this skill when an AI agent needs to plan, build, commit, monitor, or follow up on FlashRev-powered email outreach via the flashrev-mailer npm CLI (v2.0+...

by FlashLabs-AI20 installs1 stars

Lead enrichment, company enrichment, work-email finder & email validation for AI agents — plus company firmographics, domain/DNS/WHOIS intelligence, and Link...

1 installs

Enrich a list of companies (names, domains, or LinkedIn URLs) with firmographic data like industry, employee count, headquarters, and founding year. Use when the user wants to enrich, look up, or research a batch of companies.

1 installs

Enrich a list of prospects (profile URLs, slugs, or names) with LinkedIn role and experience data via Veezee. Use when the user wants current titles, companies, or work history for known people, or asks to "enrich" leads/prospects/candidates.

1 installs

Turn a role or ICP spec into a ranked outbound sales list of LinkedIn prospects, each enriched with company context and a one-line personalized opener rationale. Use whenever the user wants prospects, leads, a target-account list, people to sell to, or personalized outreach angles, even if they only describe the buyer ("find me VPs of Sales at fintech startups") without saying "list" or "outreach". For a hiring shortlist use candidate-sourcing instead; for enriching prospects the user already has, use prospect-enrich.

1 installs

Search for people at a company by domain and role keywords. Returns obfuscated profiles (first name, last-name initial, title, company). No email addresses....

1 installs