gtm-meta-skill

📁 deepline/com 📅 8 days ago
29
总安装量
29
周安装量
#7170
全站排名
安装命令
npx skills add https://code.deepline.com

Agent 安装分布

opencode 29
claude-code 29
codex 29
cursor 29
droid 25

Skill 文档

GTM Meta Skill

Use this skill for prospecting, account research, contact enrichment, verification, lead scoring, personalization, and campaign activation.

Getting Started

  1. Discover available tools and inspect one before execution:
deepline auth status
deepline tools list --json
deepline tools get apollo_people_search --json
  1. Run a tool directly:
deepline tools execute apollo_people_search \
  --payload '{"person_titles":["Head of Growth"],"q_organization_domains_list":["openai.com"],"include_similar_titles":true,"per_page":2,"page":1}' \
  --json
  1. Run an enrichment workflow:
deepline enrich --input leads.csv --output leads.csv.out.csv.out.csv \
  --with 'company_lookup=crustdata_companydb_autocomplete:{"field":"company_name","query":"{{Company}}","limit":1}' \
  --with 'people_search=apollo_people_search:{"person_titles":["VP Sales"],"q_keywords":"{{Company}}","include_similar_titles":true,"per_page":1,"page":1}'
  1. Execute a local tool with typed payload:
deepline tools execute run_javascript \
  --payload '{"code":"return (row[\"Company\"]||\"Unknown\").toUpperCase();"}' \
  --json

Pre-Flight: Inspect Tool Output Shapes

Before writing any --with JS extractor, always inspect the tool’s sample response:

deepline tools get leadmagic_email_finder --json | jq '.samples.response.payload'
# → {"data":{"email":"...","status":"valid",...}}

This tells you the exact field names, nesting (e.g. .data.email vs .email), and whether data is wrapped in .data. One inspection saves multiple enrich-debug cycles. If samples drift from live responses, run a small enrich window and inspect compact structured preview output (--json) before adding downstream columns.

When you have real run output JSON, use deepline inspect to avoid jq path guessing:

deepline inspect run-output.json
deepline inspect run-output.json --path 'result.data[].company_url'
deepline inspect run-output.json --path 'result.data.data.email_status' --json

deepline inspect gives you path/type summaries and validates whether a path exists, including sample values and null rate.

Shape reference (common enrichment tools)

Tool Input fields Key output paths
leadmagic_email_finder first_name, last_name, domain .data.email, .data.status
leadmagic_profile_search profile_url .data.company_website, .data.full_name, .data.work_experience[0].company_website
leadmagic_email_validation email .data.email_status, .data.is_domain_catch_all, .data.mx_provider
crustdata_person_enrichment linkedinProfileUrl .data[0].name, .data[0].email, .data[0].current_employers[0].employer_company_website_domain[0]

Credit Management (Billing)

Use these commands to inspect credit usage, set a monthly safety limit, disable the limit, and check current balance.

Required User Approval Before Paid Runs

Before executing any provider/tool action that can consume credits, the agent must ask for explicit user approval first.

The approval request must include:

  1. Provider name (for example: Apollo, Crustdata, LeadMagic, Apify).
  2. Task summary (what action will run and why).
  3. Estimated credits for this run (or a clear min/max range if exact cost is unknown).
  4. Scope size (expected rows/items/records to process).
  5. Maximum spend cap for this run.

Do not run paid actions until the user explicitly confirms.

# View current usage for the billing period
deepline billing --get-usage

# Set a monthly credit limit (example: 5000 credits)
deepline billing --set-monthly-limit 5000

# Disable the monthly credit limit
deepline billing --off

# Check remaining/current credit balance
deepline billing balance

JSON output variants:

deepline billing --get-usage --json
deepline billing --set-monthly-limit 5000 --json
deepline billing --off --json
deepline billing balance --json

What This Skill Helps You Do

  • Build account, lead, and contact sheets with high-signal GTM data.
  • Enrich rows with firmographic, technographic, role, intent, and contact-level attributes.
  • Verify deliverability before outbound to reduce bounce risk.
  • Score and prioritize leads for sequencing and routing.
  • Generate custom targeting and messaging inputs from enriched context.
  • Push qualified leads into outbound systems.
  • Run spreadsheet-style local batch enrichments for large jobs.

Structured Output + Research Patterns

Use structured output when you need deterministic columns for downstream scoring, routing, or outbound personalization.

When to use which provider

  • Use google_search_google_search for broad recall and URL discovery.
  • Use exa_answer for fast, schema-constrained answers with citations.
  • Use exa_research for deeper multi-source synthesis with strict JSON schema.
  • Use parallel_extract when you already have target URLs and need field extraction.
  • Use parallel_run_task for richer web research tasks with custom output_schema.

Funding and unique GTM signals to capture

  • Funding recency: last round type/date/amount, investor names, use-of-proceeds hints.
  • Hiring acceleration: open roles in sales, revops, solutions, partnerships, AI.
  • Job postings and job descriptions: stack listed in job descriptions != stack listed on website, and usually higher signal.
  • Org change: new VP/CRO/CMO hires, recent promotions, team expansions.
  • Product + packaging shifts: new SKUs, pricing page changes, enterprise plans.
  • Stack changes: newly adopted tools in data, CRM, support, analytics, AI.
  • Geographic expansion: new regions, offices, market-entry announcements.
  • Compliance/security events: SOC2, ISO, HIPAA, FedRAMP, procurement readiness.
  • Partnership/channel motion: SI alliances, marketplace listings, reseller updates.

Quality guardrails for structured output

  • Always include source URLs per extracted claim.
  • Keep nullable fields explicit (null) instead of inventing values.
  • Add a confidence field for each non-trivial claim when possible.
  • Prefer exact entity matching (name + domain + LinkedIn) to avoid person/company mixups.

Typical GTM Use Cases

  • ICP account build: enrich firmographics and hiring signals, then rank by fit.
  • Persona coverage mapping: find people at target accounts and fill contact gaps.
  • Inbound form qualification: enrich + verify + score + route.
  • Outbound list prep: search + enrich + verify + personalize + push.
  • Re-segmentation: re-enrich existing CRM/CSV lists for expansion/reactivation.
  • Clay-style workflows: block-based spreadsheet enrichments with CSV + API endpoints.

Provider Playbooks

  • apify playbook Summary: Use async run plus polling for long jobs; keep sync runs for small bounded actor inputs. Last reviewed: 2026-02-11

  • apollo playbook Summary: Recall-first people/company search with include_similar_titles=true unless strict mode is explicitly requested. Last reviewed: 2026-02-11

  • crustdata playbook Summary: Start with free autocomplete and default to fuzzy contains operators (.) for higher recall. Last reviewed: 2026-02-11

  • exa playbook Summary: Use search/contents before answer for auditable retrieval, then synthesize with explicit citations. Last reviewed: 2026-02-11

  • google_search playbook Summary: Use Google Search for broad web recall, then follow up with extraction/enrichment tools for structured workflows. Last reviewed: 2026-02-12

  • heyreach playbook Summary: Resolve campaign IDs first, then batch inserts and confirm campaign stats after writes. Last reviewed: 2026-02-11

  • instantly playbook Summary: List campaigns first, then add contacts in controlled batches and verify downstream stats. Last reviewed: 2026-02-11

  • leadmagic playbook Summary: Treat validation as gatekeeper and run email-pattern waterfalls before escalating to deeper enrichment. Last reviewed: 2026-02-11

  • lemlist playbook Summary: List campaign inventory first and push contacts in small batches with post-write stat checks. Last reviewed: 2026-02-11

  • parallel playbook Summary: Prefer run-task/search/extract primitives and avoid monitor/stream complexity for agent workflows. Last reviewed: 2026-02-11

  • peopledatalabs playbook Summary: Use clean/autocomplete helpers to normalize input before costly person/company search and enrich calls. Last reviewed: 2026-02-11

Recommended Waterfalls (Inspiration)

Waterfalls let you try multiple providers sequentially and stop at the first valid result. Use these patterns as inspiration—adjust provider order based on your audience, data quality, and budget.

Use waterfalls freely and without fear, much preferred over single provider calls always.

General Guidance

Email Finding:

  • When you have first name, last name, and company domain → use a JS block to generate email patterns (first.last@, f.last@, etc.), then a validation waterfall with LeadMagic: validate pattern 1, then 2, then 3—stop at first valid. Totally valid.
  • If you have an API-sourced email (Apollo, Crust, PDL) → extract the email from the JSON, then validate once in a separate block.
  • For provider fallback (finding): Apollo → PDL (both support name+company). Extract email from the winner, then validate. Do not validate after each provider attempt (saves credits).
  • For SMB/local businesses → Apollo often has better coverage than enterprise-focused tools

Contact Enrichment:

  • When you have a LinkedIn URL → Crustdata person enrichment is fastest and most comprehensive, followed by PDL. Use waterfalls.
  • If LinkedIn URL fails → LeadMagic profile search as fallback
  • For mobile phones → LeadMagic mobile finder works well with LinkedIn URLs

Company Discovery:

  • When you need to find people at a company → Apollo people search with title filters is free but lower quality, followed by Crust. PDL has more sophisticated filters and the best coverage. Generally use low limits=1 and responses will return total num results in the set.
  • For company enrichment → Crustdata autocomplete is fast; Apollo enrichment for fallback
  • If structured data fails → Google Search with site:linkedin.com filters

LinkedIn URL Finding:

  • From email → Google Search usually finds it faster than profile enrichment APIs but easy to get the wrong person.
  • From name only → Google Search with site filter, then People Data Labs for fuzzy matching. More info you give it the better.
  • For company pages → Crustdata autocomplete includes LinkedIn URLs; Google as fallback

Example 1: Provider Fallback (Find) + Extract + Verify

Input CSV has: First Name, Last Name, Company

Provider fallback: try Apollo, then PDL (both support name+company). Extract the email from whichever succeeds, then validate once.

deepline enrich --input leads.csv --output leads.csv.out.csv \
  --with-waterfall "email_lookup" \
  --with 'apollo=apollo_people_match:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","organization_name":"{{Company}}"}' \
  --with 'pdl=peopledatalabs_enrich_contact:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","company":"{{Company}}"}' \
  --end-waterfall \
  --with 'extract_email=run_javascript:{"code":"const e=row[\"email_lookup\"]||{}; return e?.data?.person?.email||e?.emails?.[0]?.address||null;"}' \
  --with 'verification=leadmagic_email_validation:{"email":"{{extract_email}}"}' \
  --json

Example 2: LinkedIn Enrichment with Nested Extraction (JavaScript)

Input CSV has: LinkedIn URL

This shows how to use JavaScript to extract deeply nested values from waterfall results. The waterfall tries multiple providers, then JavaScript extracts the best email from nested response structures:

deepline enrich --input leads.csv --output leads.csv.out.csv \
  --with-waterfall "contact_enrich" \
  --with 'crustdata=crustdata_person_enrichment:{"linkedinProfileUrl":"{{LinkedIn URL}}","fields":["business_email","email","mobile_phone"],"enrichRealtime":true}' \
  --with 'pdl=peopledatalabs_enrich_contact:{"linkedin_url":"{{LinkedIn URL}}"}' \
  --end-waterfall \
  --with 'extract_email=run_javascript:{"code":"const enrich = row[\"contact_enrich\"] || {}; const crustEmail = enrich?.data?.[0]?.email || enrich?.data?.[0]?.business_email; const pdlEmail = enrich?.emails?.[0]?.address; return crustEmail || pdlEmail || null;"}' \
  --with 'extract_company=run_javascript:{"code":"const enrich = row[\"contact_enrich\"] || {}; const crustCompany = enrich?.data?.[0]?.current_employers?.[0]?.employer_company_website_domain?.[0]; const pdlCompany = enrich?.job_company_website; return crustCompany || pdlCompany || null;"}' \
  --with 'verification=leadmagic_email_validation:{"email":"{{extract_email}}"}' \
  --json

What this demonstrates:

  • Waterfall returns nested JSON from whichever provider succeeds (Crustdata or PDL)
  • JavaScript blocks access waterfall results using row["contact_enrich"]
  • Path chaining extracts deeply nested values: enrich?.data?.[0]?.current_employers?.[0]?.employer_company_website_domain?.[0]
  • Fallback logic tries Crustdata structure first, then PDL structure
  • Downstream blocks (verification) use the extracted scalar values

Example 3: Validation Waterfall (LeadMagic validate candidates in order)

Input CSV has: First Name, Last Name, Company (or domain)

Generate email patterns with JS, then validate each in order—stop at first valid. Step-by-step or one call:

Complete one-liner:

deepline enrich --input leads.csv --output leads.csv.out.csv \
  --with 'patterns=run_javascript:{"code":"const f=(row[\"First Name\"]||\"\").trim().toLowerCase(); const l=(row[\"Last Name\"]||\"\").trim().toLowerCase(); const d=(row[\"Company\"]||\"\").replace(/[^a-z0-9.-]/gi,\"\"); if(!f||!l||!d) return {p1:\"\",p2:\"\",p3:\"\"}; return {p1:f+\".\"+l+\"@\"+d, p2:f[0]+l+\"@\"+d, p3:f+l[0]+\"@\"+d};"}' \
  --with-waterfall "validated" \
  --with 'v1=leadmagic_email_validation:{"email":"{{patterns.p1}}"}' \
  --with 'v2=leadmagic_email_validation:{"email":"{{patterns.p2}}"}' \
  --with 'v3=leadmagic_email_validation:{"email":"{{patterns.p3}}"}' \
  --end-waterfall \
  --with 'final_email=run_javascript:{"code":"const r=row[\"validated\"]||{}; const e=r?.data?.email||r?.email; if(e&&r?.data?.email_status===\"valid\") return e; return null;"}' \
  --json

Step-by-step (same flow, add columns one at a time):

# Step 1: Add patterns column, pilot on 2 rows
deepline enrich --input leads.csv --output leads.csv.out.csv \
  --with 'patterns=run_javascript:{"code":"const f=(row[\"First Name\"]||\"\").trim().toLowerCase(); const l=(row[\"Last Name\"]||\"\").trim().toLowerCase(); const d=(row[\"Company\"]||\"\").replace(/[^a-z0-9.-]/gi,\"\"); return {p1:f+\".\"+l+\"@\"+d, p2:f[0]+l+\"@\"+d, p3:f+l[0]+\"@\"+d};"}' \
  --rows 0:1

# Step 2: Add validation waterfall (same v1/v2/v3 as above)
deepline enrich --input leads.csv.out.csv --output leads.csv.out.csv.out.csv \
  --with-waterfall "validated" \
  --with 'v1=leadmagic_email_validation:{"email":"{{patterns.p1}}"}' \
  --with 'v2=leadmagic_email_validation:{"email":"{{patterns.p2}}"}' \
  --with 'v3=leadmagic_email_validation:{"email":"{{patterns.p3}}"}' \
  --end-waterfall \
  --rows 0:1

# Step 3: Add final_email resolver, run all rows
deepline enrich --input leads.csv.out.csv.out.csv --output leads.csv.final.csv \
  --with 'final_email=run_javascript:{"code":"const r=row[\"validated\"]||{}; const e=r?.data?.email||r?.email; if(e&&r?.data?.email_status===\"valid\") return e; return null;"}' \
  --json

Workflow Command Examples

1. Qualification + Email Design (Structured, ICP File-Guided)

  • Objective: Load ICP guidance from a local file, score qualification with explicit rationale, and generate a structured 4-email sequence. Build iteratively: pilot qualification on 1 row, inspect, then add email sequence on all rows.
  • User asks: use local icp.md + person/company context to produce structured qualification and email sequence outputs

Command chain:

deepline tools get read_file --json && deepline tools get call_ai_codex --json && \
  deepline tools execute read_file --payload '{"path":"./icp.md"}' --json && \
  python3 - <<'PY'
import csv
import json
from pathlib import Path

raw = Path('/tmp/deepline-tools-execute-read_file.json').read_text(encoding='utf-8')
obj = json.loads(raw)
result = obj.get('result') if isinstance(obj, dict) else {}
data = result.get('data') if isinstance(result, dict) else {}
icp_payload = ''
if isinstance(data, dict):
    icp_payload = str(data.get('content') or data.get('text') or '')
if not icp_payload and isinstance(result, dict):
    icp_payload = str(result.get('content') or result.get('text') or '')

prospect_payload = {
    'person': {
        'firstName': 'Rachael',
        'lastName': 'Foster',
        'title': 'Vice President AMER Field Marketing, Public Sector, Services, & Community',
    },
    'company': {
        'name': 'Cloudera',
        'domain': 'cloudera.com',
    },
}

with open('./qualification_email_seed.csv', 'w', newline='', encoding='utf-8') as f:
    w = csv.DictWriter(f, fieldnames=['prospect_payload', 'icp_payload'])
    w.writeheader()
    w.writerow({'prospect_payload': json.dumps(prospect_payload), 'icp_payload': icp_payload})
PY && \
  deepline enrich --input ./qualification_email_seed.csv --output ./qualification_email_seed-output-1.csv --with 'qualification_output=call_ai_codex:{"prompt":"You are Deepline Qualification Engine. Return strict JSON only with this shape: {data:{score:number,score_label:string,fit_band:string,rationale:string,qualification:{answers:[{question:string,answer:\"Yes\"|\"No\"|\"Unknown\",confidence:\"HIGH\"|\"MEDIUM\"|\"LOW\",rationale:string}],summary:{positives:[string],risks:[string],next_checks:[string]}}}}. Use high recall defaults unless explicitly asked for strict matching. Inputs:\nICP file payload: {{icp_payload}}\nProspect payload: {{prospect_payload}}","model":"gpt-5","json_mode":true}' --json && \
  deepline enrich --input ./qualification_email_seed-output-1.csv --output ./qualification_email_seed-output-2.csv  --with 'email_sequence_output=call_ai_codex:{"prompt":"You are Deepline Email Design Engine. Return strict JSON only with this shape: {data:{emails:[{step:number,subject:string,coreValueProp:string,email:string}],sequence_rationale:string}}. Write 4 concise emails tied to qualification rationale and explicit pains. Inputs:\nICP file payload: {{icp_payload}}\nQualification payload: {{qualification_output}}\nProspect payload: {{prospect_payload}}","model":"gpt-5","json_mode":true}' --json

2. Deep Research 5 Queries via Enrich (Exa + Parallel + Codex)

  • Objective: Run row-wise deep research across 5 queries by chaining Exa search, Parallel research, and Codex synthesis. Build iteratively: pilot exa on 2 rows, pilot parallel on 2 rows, then run codex on all.
  • User asks: use enrich to run exa + parallel + codex on five search rows and return research briefs

Command chain:

printf "Search Query\nseries b devtools saas in united states\nseries b fintech saas in united states\nseries b cybersecurity saas in united states\nseries b hr tech saas in united states\nseries b vertical ai saas in united states\n" > ./deep_research_enrich_5.csv && \
  deepline enrich --input ./deep_research_enrich_5.csv --output ./deep_research_enrich_5-output-1.csv --rows 0:1 --with 'exa_search=exa_search_companies:{"query":"{{Search Query}}","numResults":5,"type":"fast"}' --json && \
  deepline enrich --input ./deep_research_enrich_5-output-1.csv --output ./deep_research_enrich_5-output-2.csv --rows 0:1 --with 'parallel_search=parallel_search:{"objective":"Find concrete evidence for this query: {{Search Query}}","processor":"base","max_results":5}' --json && \
  deepline enrich --input ./deep_research_enrich_5-output-2.csv --output ./deep_research_enrich_5-output-3.csv --with 'research_brief=call_ai_codex:{"prompt":"Using EXA output: {{exa_search}} and PARALLEL output: {{parallel_search}}, produce a concise deep-research note with: 1) top 3 companies, 2) why each fits, 3) confidence high/med/low.","model":"gpt-5","json_mode":true}' --json

3. OpenAI 5 Contacts + Apollo Enrich + Interpolated Patterns + LeadMagic Waterfall

  • Objective: Find 5 OpenAI contacts via Apollo, generate interpolated email candidates, run LeadMagic in ordered waterfall (confirm Apollo email first, then pattern candidates), and extract a final email. Build iteratively: pilot on 2 rows, then run all rows.
  • User asks: create a 5-contact openai sheet via apollo enrichment, run interpolated patterns, leadmagic waterfall (apollo email first, then other patterns), then output final email

Command chain:

deepline tools get apollo_search_people --json | jq '.samples.response.payload' && deepline tools get apollo_people_match --json | jq '.samples.response.payload' && deepline tools get leadmagic_email_validation --json | jq '.samples.response.payload' && \
  deepline tools execute apollo_search_people --payload '{"filters":{"q_organization_domains_list":["openai.com"],"include_similar_titles":true},"limit":25,"offset":0}' --json && \
  jq -r '["person_id","first_name","domain"], ((((.result.data // {}) as $d | ($d.people // $d.results // $d.items // (if ($d|type)=="array" then $d else [] end))) | .[0:5]) | .[] | [.id, (.first_name // ""), "openai.com"]) | @csv' /tmp/deepline-tools-execute-apollo_search_people.json > ./openai_contacts_seed.csv && \
  deepline enrich --input ./openai_contacts_seed.csv --output ./openai_contacts_seed-output-pilot.csv --rows 0:1 --with 'apollo_match=apollo_people_match:{"id":"{{person_id}}","first_name":"{{first_name}}","domain":"{{domain}}"}' --with 'contact_extract=run_javascript:{"code":"const parse=(v)=>{if(v==null)return null; if(typeof v===\"object\") return v; if(typeof v!==\"string\") return null; const t=v.trim(); if(!t) return null; try{return JSON.parse(t);}catch{return null;}}; const m=parse(row?.apollo_match); const p=(m?.data?.person)||m?.person||{}; return {first_name:String(p?.first_name||row?.first_name||\"\").toLowerCase(),last_name:String(p?.last_name||\"\").toLowerCase(),domain:String(row?.domain||\"\").toLowerCase()};"}' --with 'apollo_email_candidate=run_javascript:{"code":"const parse=(v)=>{if(v==null)return null; if(typeof v===\"object\") return v; if(typeof v!==\"string\") return null; const t=v.trim(); if(!t) return null; try{return JSON.parse(t);}catch{return null;}}; const m=parse(row?.apollo_match); const p=(m?.data?.person)||m?.person||{}; return String(p?.email||\"\").trim();"}' --with 'email_patterns=run_javascript:{"code":"const f=row?.contact_extract?.first_name||\"\"; const l=row?.contact_extract?.last_name||\"\"; const d=(row?.contact_extract?.domain||\"\").replace(/[^a-z0-9.-]/g,\"\"); if(!f||!l||!d) return {perm1:\"\",perm2:\"\",perm3:\"\"}; return {perm1:f+\".\"+l+\"@\"+d,perm2:f+\".\"+l.slice(0,1)+\"@\"+d,perm3:f.slice(0,1)+\".\"+l.slice(0,1)+\"@\"+d};"}' --with-waterfall "leadmagic-confirm-apollo-then-patterns" --with 'verify_apollo_email=leadmagic_email_validation:{"email":"{{apollo_email_candidate}}"}' --with 'verify_pattern_1=leadmagic_email_validation:{"email":"{{email_patterns.perm1}}"}' --with 'verify_pattern_2=leadmagic_email_validation:{"email":"{{email_patterns.perm2}}"}' --with 'verify_pattern_3=leadmagic_email_validation:{"email":"{{email_patterns.perm3}}"}' --end-waterfall --with 'final_email=run_javascript:{"code":"const parse=(v)=>{if(v==null)return null; if(typeof v===\"object\") return v; if(typeof v!==\"string\") return null; const t=v.trim(); if(!t) return null; try{return JSON.parse(t);}catch{return null;}}; const picks=[row?.verify_apollo_email,row?.verify_pattern_1,row?.verify_pattern_2,row?.verify_pattern_3]; for(const p of picks){const o=parse(p); const e=String(o?.data?.email||o?.email||\"\").trim(); if(e) return e;} return String(row?.apollo_email_candidate||\"\").trim();"}' --json && \
  python3 - <<'PY'
import csv, json
p='./openai_contacts_seed-output-pilot.csv'
rows=list(csv.DictReader(open(p,newline='')))
name_to_header={}
for h in rows[0].keys():
  if h.startswith('{') and '"name"' in h:
    try:
      j=json.loads(h)
      if j.get('name'): name_to_header[j['name']]=h
    except Exception:
      pass
print('pilot_rows=',len(rows))
for r in rows:
  print((r.get('first_name') or '').strip(), '| candidate=', (r.get(name_to_header.get('apollo_email_candidate','')) or '').strip(), '| final=', (r.get(name_to_header.get('final_email','')) or '').strip())
PY && \
  deepline enrich --input ./openai_contacts_seed.csv --output ./openai_contacts_seed-output-full.csv --all --with 'apollo_match=apollo_people_match:{"id":"{{person_id}}","first_name":"{{first_name}}","domain":"{{domain}}"}' --with 'contact_extract=run_javascript:{"code":"const parse=(v)=>{if(v==null)return null; if(typeof v===\"object\") return v; if(typeof v!==\"string\") return null; const t=v.trim(); if(!t) return null; try{return JSON.parse(t);}catch{return null;}}; const m=parse(row?.apollo_match); const p=(m?.data?.person)||m?.person||{}; return {first_name:String(p?.first_name||row?.first_name||\"\").toLowerCase(),last_name:String(p?.last_name||\"\").toLowerCase(),domain:String(row?.domain||\"\").toLowerCase()};"}' --with 'apollo_email_candidate=run_javascript:{"code":"const parse=(v)=>{if(v==null)return null; if(typeof v===\"object\") return v; if(typeof v!==\"string\") return null; const t=v.trim(); if(!t) return null; try{return JSON.parse(t);}catch{return null;}}; const m=parse(row?.apollo_match); const p=(m?.data?.person)||m?.person||{}; return String(p?.email||\"\").trim();"}' --with 'email_patterns=run_javascript:{"code":"const f=row?.contact_extract?.first_name||\"\"; const l=row?.contact_extract?.last_name||\"\"; const d=(row?.contact_extract?.domain||\"\").replace(/[^a-z0-9.-]/g,\"\"); if(!f||!l||!d) return {perm1:\"\",perm2:\"\",perm3:\"\"}; return {perm1:f+\".\"+l+\"@\"+d,perm2:f+\".\"+l.slice(0,1)+\"@\"+d,perm3:f.slice(0,1)+\".\"+l.slice(0,1)+\"@\"+d};"}' --with-waterfall "leadmagic-confirm-apollo-then-patterns" --with 'verify_apollo_email=leadmagic_email_validation:{"email":"{{apollo_email_candidate}}"}' --with 'verify_pattern_1=leadmagic_email_validation:{"email":"{{email_patterns.perm1}}"}' --with 'verify_pattern_2=leadmagic_email_validation:{"email":"{{email_patterns.perm2}}"}' --with 'verify_pattern_3=leadmagic_email_validation:{"email":"{{email_patterns.perm3}}"}' --end-waterfall --with 'final_email=run_javascript:{"code":"const parse=(v)=>{if(v==null)return null; if(typeof v===\"object\") return v; if(typeof v!==\"string\") return null; const t=v.trim(); if(!t) return null; try{return JSON.parse(t);}catch{return null;}}; const picks=[row?.verify_apollo_email,row?.verify_pattern_1,row?.verify_pattern_2,row?.verify_pattern_3]; for(const p of picks){const o=parse(p); const e=String(o?.data?.email||o?.email||\"\").trim(); if(e) return e;} return String(row?.apollo_email_candidate||\"\").trim();"}' --json

4. Enrich Five Leads + Targeted Messaging with Claude

  • Objective: Find leads row-by-row and generate targeted messaging per lead. Build iteratively: pilot lead_search on 2 rows to inspect shape; then run targeted_message on all rows.
  • User asks: enrich five leads + find targeted messaging with claude

Command chain:

printf "domain,title\nopenai.com,Head of Growth\nopenai.com,VP Marketing\nopenai.com,Director Demand Gen\nopenai.com,Head of Sales\nopenai.com,Growth Marketing Lead\n" > ./lead_message_targets.csv && \
  deepline enrich --input ./lead_message_targets.csv --output ./lead_message_targets-output-1.csv --rows 0:1 --with 'lead_search=apollo_people_search:{"q_organization_domains_list":["{{domain}}"],"person_titles":["{{title}}"],"per_page":1,"page":1}' --json && \
  deepline enrich --input ./lead_message_targets-output-1.csv --output ./lead_message_targets-output-2.csv --with 'targeted_message=call_ai:{"agent":"claude","prompt":"Given this lead search result: {{lead_search}}. Return strict JSON with keys opener and pain_hypothesis. Keep opener under 30 words and specific to the lead role + company context.","model":"sonnet","json_mode":true}' --json

5. Build ICP Accounts + Personalized Openers

  • Objective: Find five ICP accounts, enrich company context, then generate first-line personalization.
  • User asks: find five ICP accounts, enrich them, and produce personalized first lines

Command chain:

deepline tools get exa_search_companies --json && deepline tools get call_ai --json && \
  deepline tools execute exa_search_companies --payload '{"query":"series b saas companies in united states","numResults":5,"type":"fast"}' --json && \
  deepline tools execute call_ai --payload '{"agent":"claude","model":"sonnet","prompt":"Read /tmp/deepline-tools-execute-exa_search_companies.json. Return markdown with 5 accounts and 3 personalized outbound openers each."}' --json

6. CrustData Mixed Flow: Normal + Waterfall + Normal

  • Objective: Create a lightweight CSV, run a normal enrich block, then a scoped waterfall fallback block, then a trailing normal block. Build iteratively: pilot prefix + waterfall on 2 rows, then add final_note on all rows.
  • User asks: run enriches before and after a waterfall block in one CLI command

Command chain:

printf "country_prefix,title_prefix\nUS,Eng\nCA,Sal\nGB,Prod\n" > ./waterfall_autocomplete.csv && \
  deepline tools get crustdata_companydb_autocomplete --json | jq '.samples.response.payload' && deepline tools get crustdata_persondb_autocomplete --json | jq '.samples.response.payload' && \
  deepline enrich --input ./waterfall_autocomplete.csv --output ./waterfall_autocomplete-output-1.csv --rows 0:1 --with 'prefix_upper=run_javascript:{"code":"return (row.country_prefix||\"\").toUpperCase();"}' --with-waterfall "autocomplete-fallback" --with 'company_country_codes=crustdata_companydb_autocomplete:{"field":"hq_country","query":"{{prefix_upper}}","limit":5}' --with 'person_titles=crustdata_persondb_autocomplete:{"field":"current_employers.title","query":"{{title_prefix}}","limit":5}' --end-waterfall --json && \
  deepline enrich --input ./waterfall_autocomplete-output-1.csv --output ./waterfall_autocomplete-output-2.csv --with 'final_note=run_javascript:{"code":"const byName=row[\"company_country_codes\"]; const byIndex=row[\"2\"]; const byAlias=row[\"c\"]; const src=byName||byIndex||byAlias; const first=src?.data?.values?.[0]||\"\"; return first ? (\"country=\"+first) : \"done\";"}' --json

7. Autocomplete API Smoke Check (Free) + Waterfall Prep

  • Objective: Verify free autocomplete APIs return data and then wire their outputs into an enrich waterfall plan.
  • User asks: test autocomplete endpoints first, then prepare waterfall enrich columns with the same tools

Command chain:

deepline tools execute crustdata_companydb_autocomplete --payload '{"field":"hq_country","query":"US","limit":3}' --json && \
  deepline tools execute crustdata_persondb_autocomplete --payload '{"field":"current_employers.title","query":"Eng","limit":3}' --json && \
  jq '{company:(input.result.data[0]? // null), person:(.result.data[0]? // null)}' /tmp/deepline-tools-execute-crustdata_companydb_autocomplete.json /tmp/deepline-tools-execute-crustdata_persondb_autocomplete.json && \
  echo "Use --with-waterfall in deepline enrich to chain these tools as ordered fallback block columns."

8. One-Time Apify Company Scrape -> Row-Level Email Patterns + LeadMagic (Playground Bones)

  • Objective: Run a one-time Apify company scrape to seed employee rows, then build a non-running playground workflow with one email-permutations object column and waterfall-scoped LeadMagic validation columns.
  • User asks: set up bones only: run apify once to generate openai employee rows, put all email permutations in one object column, then validate through a leadmagic waterfall in playground ui

Command chain:

deepline tools get apify_run_actor_sync --json && deepline tools get run_javascript --json && deepline tools get leadmagic_email_validation --json && \
  deepline tools execute apify_run_actor_sync --payload '{"actorId":"REPLACE_WITH_APIFY_ACTOR_ID","input":{"companies":["https://www.linkedin.com/company/openai/"],"maxItems":50}}' --json && \
  jq -r '["company_name","company_domain","linkedin_company_url","full_name","first_name","last_name","linkedin_url"], (((.result.data // {}) as $d | ($d.results // $d.items // $d.profiles // (if ($d|type)=="array" then $d else [] end))) | .[0:50] | map(select((.fullName // .name // "") != "")) | .[] | (.fullName // .name // "") as $n | ($n | split(" ")) as $parts | [(.companyName // "OpenAI"),(.companyDomain // "openai.com"),(.companyLinkedinUrl // "https://www.linkedin.com/company/openai/"),$n,(.firstName // ($parts[0] // "")),(.lastName // ($parts[1] // "")),(.linkedinUrl // .profileUrl // "")]) | @csv' /tmp/deepline-tools-execute-apify_run_actor_sync.json > ./apify_email_leads_seed.csv && \
  deepline enrich --input ./apify_email_leads_seed.csv --output ./apify_email_leads_seed-output-1.csv --with 'contact_extract=run_javascript:{"code":"const norm=(v)=>String(v||\"\").toLowerCase().replace(/[^a-z]/g,\"\"); const first=norm(row?.first_name); const last=norm(row?.last_name); return {first_name:first,last_name:last,title:String(row?.title||\"\").trim(),linkedin_url:String(row?.linkedin_url||\"\").trim(),domain:norm(row?.company_domain)};"}' --with 'email_permutations=run_javascript:{"code":"const f=row?.contact_extract?.first_name||\"\"; const l=row?.contact_extract?.last_name||\"\"; const d=row?.contact_extract?.domain||\"\"; if(!(f&&l&&d)) return {first_last:\"\",first_initial_last:\"\",first_last_initial:\"\"}; return {first_last:f+\".\"+l+\"@\"+d,first_initial_last:f.slice(0,1)+l+\"@\"+d,first_last_initial:f+l.slice(0,1)+\"@\"+d};"}' --with-waterfall "leadmagic-email-permutations" --with 'validate_email_1=leadmagic_email_validation:{"email":"{{email_permutations.first_last}}"}' --with 'validate_email_2=leadmagic_email_validation:{"email":"{{email_permutations.first_initial_last}}"}' --with 'validate_email_3=leadmagic_email_validation:{"email":"{{email_permutations.first_last_initial}}"}' --end-waterfall --with 'debug_first_name_from_object=run_javascript:{"code":"return row?.contact_extract?.first_name || \"\";"}' --with 'debug_email_from_template=run_javascript:{"code":"return \"{{email_permutations.first_last}}\";"}' --json

9. TAM Sizing (Accounts): Apollo vs PDL vs Crustdata

  • Objective: Run tiny-limit account searches across providers, keep recall-first matching, apply anti-filters, and compare total market size signals.
  • User asks: size TAM for account list across apollo/pdl/crust with anti-filters and totals

Command chain:

deepline tools list --search company_search --json | rg 'apollo_company_search|peopledatalabs_company_search|crustdata_companydb_search' && \
  deepline tools get apollo_company_search --json && deepline tools get peopledatalabs_company_search --json && deepline tools get crustdata_companydb_search --json && \
  deepline tools execute apollo_company_search --payload '{"q_organization_name":"saas","organization_locations":["United States"],"organization_num_employees_ranges":["51,200","201,500"],"per_page":2,"page":1}' --json && \
  deepline tools execute peopledatalabs_company_search --payload '{"sql":"SELECT * FROM company WHERE website IS NOT NULL AND employee_count>=51 AND employee_count<=500","size":2}' --json && \
  deepline tools execute crustdata_companydb_search --payload '{"filters":[{"op":"and","conditions":[{"filter_type":"hq_country","type":"=","value":"USA"},{"filter_type":"linkedin_industries","type":"(.)","value":"software"},{"filter_type":"employee_metrics.latest_count","type":"=>","value":50},{"filter_type":"employee_metrics.latest_count","type":"=<","value":500},{"filter_type":"company_type","type":"not_in","value":["Non Profit"]}]}],"limit":2}' --json && \
  jq -n --argfile a /tmp/deepline-tools-execute-apollo_company_search.json --argfile p /tmp/deepline-tools-execute-peopledatalabs_company_search.json --argfile c /tmp/deepline-tools-execute-crustdata_companydb_search.json '[{provider:"apollo",sample_rows:($a.result.data.organizations|length),total_entries:($a.result.data.pagination.total_entries // $a.result.data.total_entries // null)},{provider:"peopledatalabs",sample_rows:($p.result.data.data|length),total_entries:($p.result.data.total // null)},{provider:"crustdata",sample_rows:($c.result.data.companies|length),total_entries:($c.result.data.meta.totalCount // null)}]' && \
  AP=$(jq -r '(.result.data.pagination.total_entries // .result.data.total_entries // 0)' /tmp/deepline-tools-execute-apollo_company_search.json); PDL=$(jq -r '(.result.data.total // 0)' /tmp/deepline-tools-execute-peopledatalabs_company_search.json); CR=$(jq -r '(.result.data.meta.totalCount // 0)' /tmp/deepline-tools-execute-crustdata_companydb_search.json); echo "reasoning: apollo account total_entries=$AP"; echo "reasoning: pdl account total_entries=$PDL"; echo "reasoning: crust account total_entries=$CR"; if [ "$AP" -lt 100 ]; then echo "reasoning: this apollo search is too small; broaden q_organization_name or remove size band."; fi

10. TAM Sizing (People): Seniority + Keywords + Anti-Filters

  • Objective: Generate people-list TAM estimates from higher-level filters first, using Apollo seniority + keywords, then compare with PDL/Crust anti-filters.
  • User asks: size TAM for people lists and compare provider tradeoffs with broad title matching and exclusions

Command chain:

deepline tools list --json | jq -r '.tools[]?.toolId' | rg 'apollo_search_people|peopledatalabs_person_search|crustdata_persondb_search' && \
  deepline tools get apollo_search_people --json && deepline tools get peopledatalabs_person_search --json && deepline tools get crustdata_persondb_search --json && \
  deepline tools execute apollo_search_people --payload '{"filters":{"person_seniorities":["vp","head","director","manager"],"organization_locations":["United States"],"q_keywords":"b2b saas marketing growth"},"limit":2,"offset":0}' --json > /tmp/tam_apollo_people_base.json && \
  deepline tools execute peopledatalabs_person_search --payload '{"query":{"bool":{"must":[{"term":{"location_country":"united states"}},{"term":{"job_title_role":"marketing"}}],"must_not":[{"term":{"job_company_website":"amazon.com"}}]}},"size":2}' --json && \
  deepline tools execute crustdata_persondb_search --payload '{"filters":[{"op":"and","conditions":[{"filter_type":"region","type":"(.)","value":"United States"},{"filter_type":"current_employers.title","type":"(.)","value":"marketing"},{"filter_type":"current_employers.company_website_domain","type":"not_in","value":["amazon.com","microsoft.com"]}]}],"limit":2}' --json && \
  deepline tools execute apollo_search_people --payload '{"filters":{"person_seniorities":["vp","head","director"],"organization_locations":["United States"],"q_keywords":"saas"},"limit":2,"offset":0}' --json > /tmp/tam_apollo_people_band_1.json && \
  deepline tools execute apollo_search_people --payload '{"filters":{"person_seniorities":["vp","head","director"],"organization_locations":["United States"],"q_keywords":"b2b saas"},"limit":2,"offset":0}' --json > /tmp/tam_apollo_people_band_2.json && \
  deepline tools execute apollo_search_people --payload '{"filters":{"person_seniorities":["vp","head","director"],"organization_locations":["United States"],"q_keywords":"b2b saas marketing"},"limit":2,"offset":0}' --json > /tmp/tam_apollo_people_band_3.json && \
  jq -n --argfile a /tmp/tam_apollo_people_base.json --argfile a1 /tmp/tam_apollo_people_band_1.json --argfile a2 /tmp/tam_apollo_people_band_2.json --argfile a3 /tmp/tam_apollo_people_band_3.json --argfile p /tmp/deepline-tools-execute-peopledatalabs_person_search.json --argfile c /tmp/deepline-tools-execute-crustdata_persondb_search.json '[{provider:"apollo",sample_rows:($a.result.data.people|length),total_entries:($a.result.data.pagination.total_entries // $a.result.data.total_entries // null),notes:"Start high-level with person_seniorities + q_keywords, then tighten keywords in steps."},{provider:"apollo_banding",keyword_bands:[{q_keywords:"saas",total_entries:($a1.result.data.pagination.total_entries // $a1.result.data.total_entries // null)},{q_keywords:"b2b saas",total_entries:($a2.result.data.pagination.total_entries // $a2.result.data.total_entries // null)},{q_keywords:"b2b saas marketing",total_entries:($a3.result.data.pagination.total_entries // $a3.result.data.total_entries // null)}]},{provider:"peopledatalabs",sample_rows:($p.result.data.data|length),total_entries:($p.result.data.total // null),notes:"SQL filters are explicit and good for auditable anti-filters."},{provider:"crustdata",sample_rows:($c.result.data.profiles|length),total_entries:($c.result.data.meta.totalCount // null),notes:"Use fuzzy operator (.) + not_in exclusions for broad prospecting."}]' && \
  A=$(jq -r '(.result.data.pagination.total_entries // .result.data.total_entries // 0)' /tmp/tam_apollo_people_base.json); A1=$(jq -r '(.result.data.pagination.total_entries // .result.data.total_entries // 0)' /tmp/tam_apollo_people_band_1.json); A2=$(jq -r '(.result.data.pagination.total_entries // .result.data.total_entries // 0)' /tmp/tam_apollo_people_band_2.json); A3=$(jq -r '(.result.data.pagination.total_entries // .result.data.total_entries // 0)' /tmp/tam_apollo_people_band_3.json); echo "reasoning: apollo base total_entries=$A"; echo "reasoning: apollo band saas=$A1, b2b saas=$A2, b2b saas marketing=$A3"; if [ "$A3" -lt 100 ]; then echo "reasoning: this apollo search is too small; reduce keyword specificity or drop one keyword term."; fi

Bundled Specialized Skills

  • Qualification And Email Design Skill: structured ICP-guided qualification scoring plus 4-step personalized sequence design. Focus: stronger copy based on ICP context, fit rationale, and role-specific pains. Setup best practice: keep a dedicated context/icp.md (plus qualification questions and copy rules) before running the sequence flow.

Suggested flow pattern:

  1. when building enrichments, include as much context as possible. E.g. linkedin posts, enrichments, etc — this is important to improve quality of the ai enrichments.
printf "domain,title\nopenai.com,Head of Growth\nopenai.com,VP Marketing\nopenai.com,Director Demand Gen\nopenai.com,Head of Sales\nopenai.com,Growth Marketing Lead\n" > ./lead_message_targets.csv
deepline enrich --input ./lead_message_targets.csv --output ./lead_message_targets.csv.out.csv.out.csv \
  --with 'lead_search=apollo_people_search:{"q_organization_domains_list":["{{domain}}"],"person_titles":["{{title}}"],"per_page":1,"page":1}' \
  --with 'targeted_message=call_ai_claude_code:{"prompt":"Given this lead search result: {{lead_search}}. Return strict JSON with keys opener and pain_hypothesis. Keep opener under 30 words and specific to the lead role + company context.","model":"sonnet","json_mode":true}' \
  --json

Opens the browser automatically, which is essential to guide the user through the workflow.

Fast Enrich (Recommended)

deepline enrich --input leads.csv --output leads.csv.out.csv.out.csv \
  --with 'company_lookup=crustdata_companydb_autocomplete:{"field":"company_name","query":"{{Company}}","limit":1}' \
  --with 'contact_enrich=apollo_people_match:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","organization_name":"{{Company}}"}'

What this does:

  • Adds new output columns to CSV.
  • Runs new columns immediately (--auto-run=true default).
  • Opens playground automatically.

Interpolation and Chaining

Block payload interpolation supports column names and alias-path chaining:

  • {{Column Name}} for direct column references.
  • {{0}} for numeric index references.
  • {{a.0.p}} for stable alias/path chaining across enriched JSON values (a = first column, b = second, etc).

Tool outputs are stored as full JSON objects in cells. Use interpolation paths in downstream blocks to extract the exact nested value you need.

Verify-First Mode (Large/Costly Runs)

deepline enrich --input big-leads.csv --output big-leads.csv.out.csv.out.csv \
  --with 'company_lookup=crustdata_companydb_autocomplete:{"field":"company_name","query":"{{Company}}","limit":1}' \
  --with-waterfall "contact_lookup" \
  --with 'contact_enrich=apollo_people_match:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","organization_name":"{{Company}}"}' \
  --end-waterfall \
  --with 'post_score=run_javascript:{"code":"return 1;"}' \
  --rows 0:1

Use this to create columns and open playground without auto-running all rows. Add --rows 0:1 to pilot first.

Playground CLI (No API Calls)

Use these when enrich is not currently running, or when you need targeted reruns:

deepline playground start --csv leads.csv --open
deepline playground run-block --csv leads.csv --col 9 --row-start 0 --row-end 10 --wait
deepline playground snapshot --csv leads.csv
deepline playground snapshot --csv leads.csv --json

For most workflows, prefer deepline enrich --json first to avoid extra snapshot commands and token-heavy outputs.

Working Column Patterns

1) Autocomplete

deepline enrich --input companies.csv --output companies.csv.out.csv.out.csv \
  --with 'company_autocomplete=crustdata_companydb_autocomplete:{"field":"company_name","query":"{{Company}}","limit":3}'

2) run_javascript

CODE=$(jq -nc --arg c 'const first=(row["First Name"]||"there").toString().trim(); const company=(row["Company"]||"your team").toString().trim(); return "Quick idea for "+company+": "+first+", worth a look?";' '{code:$c}')
deepline enrich --input leads.csv --output leads.csv.out.csv.out.csv \
  --with "personalized_line=run_javascript:${CODE}"

When JS gets complex, skip shell quoting and load from file:

cat > /tmp/opener.js << 'EOF'
if ((row["Company"] || "") === "Acme") return "Saw your recent launch - relevant idea for next quarter.";
return "Quick thought for " + (row["Company"] || "your team") + ".";
EOF

deepline enrich --input leads.csv --output leads.csv.out.csv.out.csv \
  --with "personalized_line=run_javascript:@/tmp/opener.js"

Important: block outputs are pre-parsed objects, not JSON strings. The playground parses stored block JSON, so row["some_column"] is already an object in JS columns (including columns created in prior enrich sessions). Use whichever selector is clearest:

  • by name: row["crust_enrich"]
  • by index: row["15"]
  • by alias: row["a"], row["b"], … (column-order aliases)

Avoid unnecessary JSON.parse() on block outputs. Access fields directly:

const byName = row["crust_enrich"];
const byAlias = row["b"]; // same column by alias
const source = byName ?? byAlias;
return source?.data?.[0]?.current_employers?.[0]?.employer_company_website_domain?.[0] || "";

3) Waterfall

deepline enrich --input leads.csv --output leads.csv.out.csv.out.csv \
  --with 'company_context=crustdata_companydb_autocomplete:{"field":"company_name","query":"{{Company}}","limit":1}' \
  --with-waterfall "email_lookup" \
  --with 'email_from_apollo=apollo_people_match:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","organization_name":"{{Company}}"}' \
  --with 'email_from_pdl=peopledatalabs_enrich_contact:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","domain":"{{Company}}"}' \
  --end-waterfall \
  --with 'final_note=run_javascript:{"code":"return \"done\";"}'

4) Apify Store actor discovery (find best scraper first)

Use this when you need to identify the right Apify Actor before running it.

Mandatory actor-selection policy when the user mentions Apify:

  1. If the user provides a specific actor (actor ID, username/name, or Apify Store URL), use that actor first.
  2. If no actor is provided, use the recommendation catalog in src/lib/integrations/apify/recommended-actors.ts and select the top-ranked actor for the matching use case.
  3. If the action is not in the recommended list, run web search to find strong candidates for the exact task.
  4. Do not use actors priced via rental; prefer actors with non-rental pricing models.
  5. Select the actor that is best on both quality and cost for the task (favor high ratings/recent usage/success signals with reasonable non-rental pricing).
  6. If multiple actors are close, present the top options briefly and default to the best value choice.
  7. Always check operatorNotes in the recommendation catalog and honor internal experience notes when they conflict with public ratings.

Recommendation source of truth:

  • src/lib/integrations/apify/recommended-actors.ts
  • Includes: useCase, pricingModel, users30Days, rating, runStats30Days, lastValidatedAt, operatorNotes, and contract.
  • contract includes: input schema/expected fields, output contract notes, sample payloads, and sync/async mode notes.
  • operatorNotes is the place to capture team/operator experience (for example: “HarvestAPI worked well for us”, “Apimaestro had reliability issues for our flow”).

Current recommended defaults (as of catalog validation):

  • LinkedIn profile scraping: dev_fusion/linkedin-profile-scraper
  • LinkedIn company employee scraping: harvestapi/linkedin-company-employees

Recommended base query (high precision for this use case):

deepline tools execute apify_list_store_actors \
  --payload '{"search":"linkedin company employees scraper","sortBy":"relevance","limit":20}' \
  --json

No-cookie variation:

deepline tools execute apify_list_store_actors \
  --payload '{"search":"linkedin company employees no cookies","sortBy":"relevance","limit":20}' \
  --json

Category-focused variation:

deepline tools execute apify_list_store_actors \
  --payload '{"search":"linkedin employee scraper","category":"SOCIAL_MEDIA","sortBy":"relevance","limit":20}' \
  --json

Avoid broad popularity-only searches for niche tasks; they pull unrelated high-traffic actors.

Quick ranking view from JSON output (users + rating + title):

deepline tools execute apify_list_store_actors \
  --payload '{"search":"linkedin company employees scraper","sortBy":"relevance","limit":25}' \
  --json | jq -r '
    .result.data.data.items
    | map({
        title,
        username,
        name,
        pricing: (.currentPricingInfo.pricingModel // "UNKNOWN"),
        users30: (.stats.totalUsers30Days // 0),
        rating: (.stats.actorReviewRating // 0)
      })
    | sort_by(-.users30, -.rating)
    | .[0:10]
    | .[]
    | [.title, .username, .name, (.users30|tostring), (.rating|tostring), .pricing]
    | @tsv'

GTM workflow summary: 1) Search candidates (apollo, exa, crustdata, peopledatalabs). 2) Enrich people/accounts/validate emails. 3) Verify contact points. 4) Score + prioritize. 5) Personalize messaging inputs. 6) Activate outbound (instantly, heyreach, lemlist).

Category Guide: Providers, Prereqs, Outputs

Search

  • Apollo: best for B2B people/company discovery by role, seniority, industry, and domain. Prereqs: filters like titles, company domain, industry, location, and headcount. Outputs: people/company profiles and firmographics; people search does not include emails/phones unless you run enrichment with reveal flags.
  • Exa: best for web search and synthesis. Prereqs: natural language query plus optional filters (domain, date, category). Outputs: URLs, snippets, and optionally full page text; answer endpoint returns synthesized responses with citations.
  • Crustdata: best for LinkedIn-centric search and job listings. Prereqs: LinkedIn URLs or structured filters (title, company, geography). Outputs: LinkedIn profile data, company context, and job listing details.
  • People Data Labs: best for structured people/company search when you have partial identifiers. Prereqs: name, company, domain, LinkedIn URL, or PDL ID. Outputs: normalized person/company profiles with match likelihood.
  • Parallel: best for web search + extract in one workflow. Prereqs: query or URL list; optional extraction objective. Outputs: search results plus cleaned markdown/excerpts from pages.
  • Apify: best for bespoke web scraping. Prereqs: actor ID plus actor-specific input schema. Outputs: dataset items and key-value store outputs defined by the actor.

Verify

  • LeadMagic email validation: best for deliverability checks at scale. Prereqs: email address. Outputs: status (valid/invalid/catch-all/unknown), MX provider, and deliverability metadata.
  • LeadMagic mobile finder: best for phone discovery when you already have a profile/email. Prereqs: LinkedIn/profile URL or email. Outputs: mobile phone number when available.
  • Apollo/PDL enrichment: best for corroborating contact data before validation. Prereqs: strong identifiers (full name + company/domain, or LinkedIn URL). Outputs: enriched emails/phones when reveal options are enabled (use validation to confirm deliverability).

Score

  • No dedicated scoring providers are registered in the integration catalog today. Use an LLM workflow (call_ai_claude_code/call_ai) after enrichment. Prereqs: enriched lead + account context and ICP criteria. Outputs: numeric score/grade with reasons and recommended next action.

Outbound

  • Instantly: best for email campaign activation and analytics. Prereqs: campaign ID and ready-to-send contacts. Outputs: push results (pushed/failed), campaign lists, and campaign stats.
  • HeyReach: best for LinkedIn outreach at scale. Prereqs: campaign ID/list and LinkedIn profile URLs. Outputs: lead add results and campaign progress signals.
  • Lemlist: best for multichannel sequences (email + LinkedIn). Prereqs: campaign ID and contact payload with personalization fields. Outputs: lead creation results and campaign performance metrics.

Run The Batch Enrichment Playground Locally

The detailed guide is in playground-guide.md. This helps you show the user the full context of the enrichment process. Very important. If sandboxed agent runs show DNS/network errors, re-run Deepline commands outside sandbox with user approval and follow the “Sandbox Networking Fallback (Important)” section in playground-guide.md.

Recommended rollout pattern for CSV enrichment

Build enrichments iteratively: add 1–2 columns, inspect output (enrich prints a snapshot after each run), then add downstream columns. Use a 1–2 row pilot with --rows 0:1 when output shape is uncertain, tool behavior is new, or matching logic is high risk.

Pilot-first is conditional, not mandatory:

  1. If the tools/output shapes are known and previously validated, run full-sheet directly with default --auto-run=true.
  2. If output shape is unknown or parsing/matching is complex, run 1–2 rows first using --rows 0:1.
  3. Validate mappings and outputs (empty fields, parsing errors, wrong company/person match, malformed JSON).
  4. Expand to all rows once the sample output is correct.

--auto-run controls whether configured columns execute immediately after startup; it is not a separate pilot mode.

CSV enrich command (auto-creates playground columns)

Use deepline enrich when you want to define enrichment columns from tool specs and immediately open the human-in-the-loop playground:

deepline enrich --input companies.csv --output companies.csv.out.csv.out.csv \
  --with 'founders=apollo_people_search:{"person_titles":["Founder"],"q_keywords":"{{Company}}","include_similar_titles":true,"per_page":2,"page":1}' \
  --with 'sales=apollo_people_search:{"person_titles":["VP Sales"],"q_keywords":"{{Company}}","include_similar_titles":true,"per_page":1,"page":1}'

What this does:

  • Resolves each --with tool using Deepline tool metadata.
  • Uses your explicit --with JSON payload as the payload_template (no implicit field auto-mapping).
  • Auto-creates block header columns directly in the CSV.
  • Auto-starts deepline playground start for that CSV.
  • Opens the browser by default and logs the exact UI URL.

To run enrichment on particular rows only, use --rows START:END (e.g. --rows 0:49) or --all:

deepline enrich --input leads.csv --output leads.csv.out.csv.out.csv \
  --with 'email=leadmagic_email_finder:{"first_name":"{{First Name}}","last_name":"{{Last Name}}","domain":"{{Company Domain}}"}' \
  --rows 0:1

Enrich is idempotent (important)

deepline enrich is designed for iterative reruns on the same CSV.

  • Re-running with the same column aliases updates existing block configs instead of duplicating columns.
  • Existing cell values are preserved unless that block is re-run for those rows.
  • This lets you build complex workflows in stages, validate each stage, then continue.

Use this rollout loop for practical waterfall flows:

  1. Add upstream columns first (e.g., patterns, or provider lookup).
  2. Add waterfall (validation or provider fallback), pilot on 2 rows.
  3. Snapshot and inspect what each block wrote.
  4. Add resolver logic (e.g., final_email) based on observed shapes.
  5. Add downstream columns (signals, messaging, outbound activation).
  6. When stable, collapse into one declarative deepline enrich ... --with-waterfall ... call.

Example: validation waterfall, step-by-step

# Stage 1: patterns + validation waterfall, pilot
deepline enrich --input leads.csv --output leads.csv.out.csv \
  --with 'patterns=run_javascript:{"code":"const f=(row[\"First Name\"]||\"\").trim().toLowerCase(); const l=(row[\"Last Name\"]||\"\").trim().toLowerCase(); const d=(row[\"Company\"]||\"\").replace(/[^a-z0-9.-]/gi,\"\"); return {p1:f+\".\"+l+\"@\"+d, p2:f[0]+l+\"@\"+d};"}' \
  --with-waterfall "validated" \
  --with 'v1=leadmagic_email_validation:{"email":"{{patterns.p1}}"}' \
  --with 'v2=leadmagic_email_validation:{"email":"{{patterns.p2}}"}' \
  --end-waterfall \
  --rows 0:1

# Stage 2: resolver
deepline enrich --input leads.csv.out.csv --output leads.csv.out.csv.out.csv \
  --with 'final_email=run_javascript:{"code":"const r=row[\"validated\"]||{}; return (r?.data?.email_status===\"valid\"&&r?.data?.email)||null;"}'

# Stage 3: downstream
deepline enrich --input leads.csv.out.csv.out.csv --output leads.csv.final.csv \
  --with 'targeted_message=call_ai:{"agent":"claude","model":"haiku","json_mode":true,"prompt":"Given {{final_email}}, return JSON."}' \
  --json

Outcome

Agents create precise account/lead/contact sheets with enriched data, then drive custom targeting and messaging with structured provider-backed workflows and reproducible playground runs.