
Cargo Gtm
- 3.3k installs
- 15 repo stars
- Updated August 3, 2026
- getcargohq/cargo-skills
cargo-gtm is a meta Claude agent skill that routes Cargo go-to-market workflows—sourcing, enrichment, verification, scoring, and outreach—through cargo-ai CLI recipes before any paid provider action runs.
About
Serves as the front door for go-to-market tasks on Cargo, routing prospect and account work to phase guides, recipes, and provider playbooks. A developer uses it when acting on ICP lists, leads, or campaign activation.
- Waterfall enrichment, lookup, scoring, CRM sync
- Signal monitoring for job changes, funding, and intent
Cargo Gtm by the numbers
- 3,311 all-time installs (skills.sh)
- +562 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #42 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/getcargohq/cargo-skills --skill cargo-gtmAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.3k |
|---|---|
| repo stars | ★ 15 |
| Last updated | August 3, 2026 |
| Repository | getcargohq/cargo-skills ↗ |
How do you run Cargo GTM prospecting workflows safely?
Run GTM tasks on Cargo: sourcing, waterfall enrichment, email/phone/LinkedIn lookup, scoring, sequencing, CRM sync, and signal monitoring.
Who is it for?
Revenue engineers and GTM developers automating Cargo sourcing, enrichment, and outreach with cargo-ai CLI guardrails.
Skip if: Developers doing free Cargo CRUD only or building unrelated application backends without prospecting workflows.
When should I use this skill?
The user needs Cargo prospecting, lead enrichment, email verification, signal monitoring, or outreach activation with cargo-ai.
What you get
Credit-budgeted lead lists, verified contacts, enrichment outputs via download-outputs URLs, and recipe-guided outreach activation plans.
- Verified lead lists
- Enrichment CSV exports
- Outreach activation plans
By the numbers
- 12 step-by-step recipes in recipes/ directory
- 6 priority GTM providers in the default stack
- 120-integration catalog with 141 credits-based actions
Files
Cargo GTM — Meta Skill
Use this skill for prospecting, account research, contact enrichment, verification, lead scoring, personalization, signal monitoring, and campaign activation.
1) What this skill governs
- Route GTM decisions, safety gates, and provider/quality defaults before execution.
- Keep long command chains and tooling nuance in sub-docs; provider-specific implementation detail in
provider-playbooks/*.md. - Anchor recipes in credits-based actions (the high-value action calls). Free CRUD (createLead, getLead, deleteRecords) doesn't need this skill — agents can compose those ad hoc.
Process / goal
The user is generally trying to go from "I have an ICP" to "Here's a list of prospects with verified emails and personalized signals." They may be anywhere in this process — guide them along.
Discovery order: companies first, then people. When the task requires finding contacts at companies matching criteria (portfolio, ICP, hiring signal), discover the company set first, then find people at each company. Don't start with broad people-search queries.
Documentation hierarchy
- Level 1 —
SKILL.md(this file): decision model, guardrails, routing table, links to sub-docs. - Level 2 — Phase docs: `guides/finding-companies-and-contacts.md`, `guides/enriching-and-researching.md`, `guides/writing-outreach.md`.
- Level 2.5 — Recipes: `recipes/*.md` — step-by-step playbooks for specific scenarios.
- Level 3 — Provider playbooks: `provider-playbooks/<slug>.md` — provider-specific quirks, costs, and fallback behavior.
2) Read behavior — MANDATORY before any execution
STOP. Do not call any provider, run any `cargo-ai orchestration action execute` command, or write any search query until you have opened the correct sub-doc for your task.
These docs encode what works, what fails, and why. They contain validated parameter schemas, cheapest-provider mappings, parallel execution patterns, sample payloads, and known pitfalls. Reading the right doc for 10 seconds saves 10 failed action calls, wasted credits, and garbage output.
Routing rules — match your task to a doc and READ IT
| When the task involves… | You MUST read this doc first | What it gives you |
|---|---|---|
| Finding companies, finding people, building lead lists, prospecting, portfolio/VC sourcing, contact finding at known companies | `guides/finding-companies-and-contacts.md` | Provider filter schemas, cheapest-source decision tree, parallel patterns, role-based search rules, portfolio/VC shortcuts, contact-finding patterns. |
| Enriching companies or contacts, finding emails/phones/LinkedIn, waterfall enrichment, signal lookup (job change, funding, tech stack), coalescing data | `guides/enriching-and-researching.md` | Waterfall patterns with fallback chains, when to use cargo-native vs waterfall vs FullEnrich vs peopleDataLabs, email/phone/LinkedIn fallback orders, signal segments, output retrieval via run download-outputs. |
| Writing cold emails, personalizing outreach, lead scoring, qualification, sequence design, campaign copy | `guides/writing-outreach.md` | LLM provider routing (openAi/anthropic/perplexity/gemini), prompt templates, scoring rubrics, email length/tone rules, personalization patterns. |
| Building or modifying a recurring workflow (cron / webhook / scheduled tool / play), designing step sequences, triggers, deploy/verify cycles | `../cargo-orchestration/SKILL.md` (capability) + apply-patterns from this skill's recipes | Schema for tool/play workflows, node graph syntax, polling strategies, output retrieval. |
Recipes: step-by-step playbooks (check before executing)
Scan this list and read the recipe matching your task. When a recipe matches: follow it step-by-step as your execution plan.
| Recipe | Use when… |
|---|---|
| `recipes/prospecting.md` | End-to-end find → enrich → verify → sync (P1/P2/P3 variants) |
| `recipes/build-tam.md` | Building a Total Addressable Market list at scale (100–10,000 companies) |
| `recipes/linkedin-url-lookup.md` | Resolving a person's LinkedIn profile URL from name + company with strict identity validation |
| `recipes/portfolio-prospecting.md` | Investor / accelerator → portfolio companies → contacts |
| `recipes/job-change-monitoring.md` | waterfall.detectJobChange (cargo-unique) on a contact segment |
| `recipes/funding-watch.md` | Tracking companies that recently raised funding |
| `recipes/tech-intent.md` | Finding companies by tech-stack or hiring-intent signals |
| `recipes/icp-discovery.md` | Diffing Closed-Won vs Closed-Lost segments to surface ICP signals |
| `recipes/outreach-activation.md` | Turning a signal segment into send-ready outreach (enrich → verify → personalize → sequencer handoff) |
| `recipes/re-engagement.md` | Waking up stale contacts only when a fresh signal fires (job change, funding, tech intent) |
| `recipes/lost-deal-revival.md` | Reviving Closed-Lost CRM deals by branching on lost_reason (champion left, budget, timing) |
| `recipes/account-expansion.md` | Multi-threading existing customer accounts — net-new buyers, deduped against the workspace's Contacts model |
If none match, scan the phase docs above for the closest pattern and adapt — or invoke `agents/execution-plan-creator.md` to compose a custom chain with provider/action slugs and cost estimates.
3) Priority provider stack (recipes lead with these 6)
These six credits-based providers cover the full prospecting → enrichment → verification → signal pipeline at the lowest credit cost in the catalog. Every recipe in this skill's recipes/ leads with this stack:
| Provider | Role | Key actions (cost in credits) |
|---|---|---|
| salesNavigator | Sourcing | searchLeads (0.02), searchAccounts (0.05), findCompanyInsights/Metrics/EmployeesCount/Distribution (0.25 each) |
| cargo (native) | Firmographic + signal intelligence | enrichBusinessFirmographics (0.5), …Technographics (1), …FundingAndAcquisitions (0.5), enrichProspectDetails/LinkedinProfile/LinkedinPosts (2), matchBusiness/matchProspect (0.5), 13 more |
| waterfall | Multi-source enrichment + signal | enrichContact (2), enrichCompany (1), verifyEmail (0.1), detectJobChange (3), searchProspects (3), findPhone (7) |
| FullEnrich | Premium contact lookup | findEmail (1), findPhone (6), findPhoneAndEmail (7), reverseEmailLookup (2) |
| theirStack | Tech-stack + hiring intent | searchTechnologies (0.5), searchJobs (0.5), searchCompanies (0.5) |
| peopleDataLabs | Heavyweight backfill | enrichPerson (3), enrichCompany (3), searchPeople (3), searchCompanies (3), queryPeople/Companies (3) |
See `provider-playbooks/` for per-provider deep dives. See `references/stage-action-map.md` for the complete cheapest-action-per-stage table across the full 120-integration catalog.
4) Recipe spine (default chain)
1. SOURCE → salesNavigator.searchLeads / searchAccounts (0.02–0.05/record)
2. DEDUPE → cargo.matchProspect / cargo.matchBusiness (0.5/record)
3. ENRICH → cargo.enrichBusinessFirmographics / Technographics
+ waterfall.enrichContact / enrichCompany (0.5–2/record)
4. SIGNAL → cargo.enrichBusinessFundingAndAcquisitions
+ theirStack.searchJobs
+ waterfall.detectJobChange (0.5–3/record)
5. CONTACT → FullEnrich.findEmail (fallback peopleDataLabs) (1–3/record)
6. VERIFY → waterfall.verifyEmail (0.1/record)
7. BACKFILL → peopleDataLabs.enrichPerson (only if step 5 missed) (3/record)Adapt by phase: drop steps that aren't relevant to the user's goal. For pure sourcing, run step 1 only. For "enrich a list I already have," run steps 2–7.
5) Output retrieval — use run download-outputs, not run download
When the agent needs the actual data produced by an action (enriched fields, found emails, search results), use:
cargo-ai orchestration run download-outputs \
--workflow-uuid <uuid> \
--output-node-slug <slug> \
--format json \
--is-finishedReturns {"url": "..."} — a signed URL to a CSV/JSON containing only the output node's data. Faster and cheaper than run download (which pulls full run records). See `references/output-retrieval.md` and `../cargo-analytics/SKILL.md`.
6) Action shape rules (every recipe)
Every action JSON in this skill follows the rules in `../cargo-orchestration/references/examples/actions.md`:
kind: "connector"action shape:{"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. `connectorUuid` is NOT in `config` — the platform resolves the workspace's authenticated connector fromintegrationSlugautomatically.- For multi-step node graphs:
connectorUuidlives at the top level of the node, not inconfig. Cross-node interpolation uses{{nodes.<slug>.<field>}}. Agent node outputs wrap under.answer(read as{{nodes.<slug>.answer.<field>}}).
7) When stuck — file a workspace report
If a recipe fails repeatedly and the cause isn't obvious, escalate via cargo-ai workspaceManagement report create. See `../cargo-workspace-management/SKILL.md` (Reports section).
8) Provider playbooks
Per-provider deep dives for the priority stack. Long-tail providers don't have dedicated playbooks yet — fall back to `references/alternatives.md` and `references/stage-action-map.md`.
Priority stack:
- `provider-playbooks/salesNavigator.md` — cheapest sourcing in the catalog (0.02–0.05/record).
- `provider-playbooks/cargo.md` — 22 native enrichment + signal actions; the
match*actions are key for dedup. - `provider-playbooks/waterfall.md` — swiss-army-knife: enrichment, verification, and the cargo-unique
detectJobChangesignal. - `provider-playbooks/FullEnrich.md` — premium contact lookup;
reverseEmailLookupis unique. - `provider-playbooks/theirStack.md` — tech-stack + hiring-intent signals.
- `provider-playbooks/peopleDataLabs.md` — heavyweight backfill at flat 3-credit tier.
9) References
- `references/stage-action-map.md` — cheapest credits-based action per stage across the full 120-integration catalog.
- `references/credits-cost-table.md` — auto-generated cost table for all 141 credits-based actions.
- `references/waterfall-strategy.md` — canonical waterfall chains by enrichment goal (every recipe's "fallback" follows these).
- `references/alternatives.md` — provider swap-ins from the long tail when the priority stack can't serve.
- `references/output-retrieval.md` —
run download-outputspatterns for fetching action data.
Agent — Execution Plan Creator
Sub-agent for cargo-gtm. Takes a user goal and returns a step-by-step plan citing specific provider + action slugs with cost estimates.
When to invoke this agent
- The user's goal touches multiple stages (sourcing → enrichment → verification → sequencing) and the right path isn't obvious.
- The user asks "what would this cost?" or wants a budget estimate before executing.
- A recipe doesn't perfectly match — you need to compose a custom chain.
For goals matching an existing recipe in ../recipes/, use the recipe directly — don't invoke this agent.
What this agent produces
A structured plan with:
1. Goal restatement — one sentence confirming intent. 2. Stage breakdown — each step labelled with stage (SOURCE / DEDUPE / ENRICH / SIGNAL / CONTACT / VERIFY / BACKFILL / WRITE-BACK / SEQUENCE / SYNC). 3. Per-step provider + action slug + cost — anchored in the priority stack where possible; long-tail providers only when priority can't serve the criteria. 4. Total credit budget — sum across steps, by record-count assumptions. 5. Open questions for the user — anything ambiguous (segment source, contact volume per company, write-back destination).
Plan template
GOAL: <one sentence>
ASSUMPTIONS (call out anything the user should confirm):
- Volume: ~N records
- ICP: <one-line>
- Output: <model write-back / CSV / CRM push>
PLAN:
Step 1 — SOURCE
Provider: salesNavigator.searchAccounts (priority)
Cost: 0.05 × N = X credits
Why this provider: ...
Step 2 — DEDUPE
Provider: cargo.matchBusiness (priority)
Cost: 0.5 × N = X credits
Step 3 — ENRICH (firmographics)
Provider: cargo.enrichBusinessFirmographics (priority)
Cost: 0.5 × N (matched) = X credits
Fallback for unmatched: waterfall.enrichCompany (1 × M = Y credits)
... (steps continue)
TOTAL BUDGET: ~X credits
OPEN QUESTIONS:
- Should we cap contacts per company at K?
- Verify priority providers are connected: <providers>Provider-selection heuristics
When choosing between providers for a stage, the agent applies these rules in order:
1. Match the priority stack first. If salesNavigator / cargo / waterfall / FullEnrich / theirStack / peopleDataLabs can express the user's filter, use them. 2. Pick by stage-action-map. If the priority stack misses, consult `../references/stage-action-map.md` for the cheapest credible alternative. 3. Consider rate limits & coverage. Some providers have low rate limits (~10 RPS); for large batches > 1000 records, prefer providers with higher throughput. 4. Confirm authentication. Run cargo-ai connection connector list --integration-slug <slug> to confirm the provider is authenticated before locking it into the plan. If not, surface to the user.
Cost discipline
Always present a cost estimate before executing. The user gets to approve / modify before the agent fans out across N records. Never skip this step for runs > 50 records.
Action shape rule (critical)
Every recipe step must use the canonical action shape: {"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`. See `../../cargo-orchestration/references/examples/actions.md`.
Output retrieval
Final step of every plan ends with cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug> — the canonical way to retrieve action results. See `../references/output-retrieval.md`.
Enriching and researching
How to enrich companies and contacts on Cargo. Covers waterfall enrichment, fallback chains, signal extraction, and output retrieval.
Default chain by enrichment goal
Goal → which provider chain?
Firmographics on a known company (industry, size, geo, revenue, …)?
├─ cargo.matchBusiness (0.5) → cargo.enrichBusinessFirmographics (0.5)
├─ Fallback for unmatched: waterfall.enrichCompany (1)
└─ Heavy backfill: peopleDataLabs.enrichCompany (3)
Contact details on a known person (title, location, social, …)?
├─ cargo.matchProspect (0.5) → cargo.enrichProspectDetails (2)
├─ Fallback: waterfall.enrichContact (2)
└─ Heavy backfill: peopleDataLabs.enrichPerson (3)
Find an email from name + company?
├─ FullEnrich.findEmail (1) ← default
├─ Cheap fallback: hunter.findEmail (0.5) / icypeas.findEmail (0.1)
└─ Last resort: peopleDataLabs.enrichPerson (3, includes email)
Verify an email?
├─ waterfall.verifyEmail (0.1) ← default (cheap, multi-source)
└─ Alt: zeroBounce.verifyEmail (0.1) / icypeas.verifyEmail (0.01)
Find a phone number?
├─ FullEnrich.findPhone (6) ← higher quality
├─ Cheap fallback: prospeo.findPhone (3)
└─ Combined: FullEnrich.findPhoneAndEmail (7) when both are needed
Resolve a LinkedIn URL from name + company?
└─ linkedin.findProfileUrl (0.25) → linkedin.enrichProfile (0.25) for validation
See `../recipes/linkedin-url-lookup.md` for the strict-validation pattern.
Funding / acquisition signals?
├─ cargo.enrichBusinessFundingAndAcquisitions (0.5)
└─ Alt: enrichCrm.getFunding (1)
Tech stack / hiring intent?
├─ cargo.enrichBusinessTechnographics (1)
├─ theirStack.searchTechnologies (0.5) for catalog-style lookup
└─ theirStack.searchJobs (0.5) for hiring-intent
Job change detection?
└─ waterfall.detectJobChange (3) — only credits-based action of this kind in catalog
Reverse-email lookup (email → person + company)?
└─ FullEnrich.reverseEmailLookup (2)Waterfall enrichment pattern
When one provider misses, escalate to the next. Run each step only on the rows where the prior step came up empty.
# Step 1 — try cargo first (cheapest + best for known companies)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFirmographics","config":{}}' \
--records '[{"domain":"acme.com"}, ... ]' \
--wait-until-finished > /tmp/step1.json
# Step 2 — extract rows where step 1 returned no firmographics, retry with waterfall
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"enrichCompany","config":{}}' \
--records '<rows from step 1 where firmographics empty>' \
--wait-until-finished > /tmp/step2.json
# Step 3 — last-resort backfill with peopleDataLabs (3 credits flat)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"enrichCompany","config":{}}' \
--records '<rows still empty after step 2>' \
--wait-until-finished > /tmp/step3.json
# Step 4 — coalesce all three into a single enriched datasetSame shape applies for person enrichment (cargo.enrichProspectDetails → waterfall.enrichContact → peopleDataLabs.enrichPerson) and for email lookup (FullEnrich.findEmail → hunter.findEmail → peopleDataLabs.enrichPerson).
Coalesce pattern (multi-pass enrichment)
When enriching the same record across multiple providers, merge results column-by-column. Prefer the higher-quality source per column:
| Column | Prefer |
|---|---|
| Firmographics (industry, size, hq) | cargo > peopleDataLabs > waterfall |
| Funding / financials | cargo.enrichBusinessFundingAndAcquisitions > enrichCrm.getFunding |
| Technographics | cargo.enrichBusinessTechnographics > theirStack > peopleDataLabs |
| FullEnrich > hunter > peopleDataLabs | |
| Phone | FullEnrich > prospeo > waterfall |
| LinkedIn URL | linkedin.findProfileUrl > FullEnrich.reverseEmailLookup |
| Job change signal | waterfall.detectJobChange (only source) |
Output retrieval — run download-outputs
After a batch run, retrieve the actual enriched data with `cargo-ai orchestration run download-outputs`, NOT run download (which gives you full run records — useful for debugging but inefficient for output extraction).
cargo-ai orchestration run download-outputs \
--workflow-uuid <uuid> \
--output-node-slug <slug> \
--batch-uuid <uuid> \
--format json \
--is-finishedReturns {"url": "..."} — a signed URL to a CSV/JSON containing only the output node's data with input/output context per record. See `../../cargo-analytics/SKILL.md` for the full reference.
For ad-hoc action execute / action execute-batch runs (no saved tool), use --wait-until-finished and read the response directly. The response shape is documented in `../../cargo-orchestration/references/response-shapes.md`. Per-node output lives at runContext.<nodeSlug> for runs and per-record output fields for batches.
Action shape rules
kind: "connector" action: {"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. `connectorUuid` is NOT in `config`. The platform resolves the workspace's authenticated connector from integrationSlug. See `../../cargo-orchestration/references/examples/actions.md`.
Polling guidance
Small runs (< 50 records): use --wait-until-finished for ergonomics.
Large runs (>= 100 records): poll. See `../../cargo-orchestration/references/polling.md` for retry strategy and rate-limit handling.
When enrichment misses
Two failure modes:
1. Coverage gap — record exists but provider doesn't have data. Walk the waterfall. 2. Quality issue — provider returns data but it's wrong. Compare two sources; if they disagree, flag the record for manual review rather than picking one blindly.
Common quality pitfalls:
- Email finders return catch-all emails that look valid but bounce. Always verify with
waterfall.verifyEmail. - LinkedIn URL resolvers return profiles for the wrong person with the same name. Use the strict-validation pattern in `../recipes/linkedin-url-lookup.md`.
- Job-change signals can show stale data on small companies. Cross-check with the contact's current LinkedIn before acting on
waterfall.detectJobChangeresults.
Finding companies and contacts
How to source accounts and people on Cargo. Covers the full sourcing decision tree, provider-by-provider strengths, and the parallel patterns that work at scale.
Decision tree
Goal → which sourcing path?
Looking for COMPANIES matching ICP criteria (industry, size, geo, …)?
├─ Cheapest at scale (0.05 cred/record): salesNavigator.searchAccounts
├─ Need rich filters / structured query: peopleDataLabs.queryCompanies (3 cred)
├─ Tech-stack or hiring intent: theirStack.searchCompanies / searchTechnologies / searchJobs (0.5 cred)
├─ Local / SMB / storefront (Maps-style): serper.searchPlaces (1 cred)
├─ Specific domain → details: cargo.matchBusiness (0.5 cred) → cargo.enrichBusinessFirmographics
└─ Already have a domain list? skip sourcing — go straight to enrichment
Looking for PEOPLE at companies?
├─ Cheapest at scale (0.02 cred/record): salesNavigator.searchLeads
├─ Rich filters / large database: peopleDataLabs.searchPeople / queryPeople (3 cred)
├─ LinkedIn-anchored: linkedin.findProfileUrl + linkedin.enrichProfile (0.25 cred)
├─ "Find people I know who can intro": theSwarm.searchWarmIntrosToCompany / Person (2 cred)
└─ Visitor de-anonymization: snitcher.searchSessions (0 cred) → cargo.matchProspect
Looking for INVESTOR-BACKED companies?
└─ peopleDataLabs.queryCompanies with investor/funding filter
(then salesNavigator.searchLeads at each portfolio company)Companies-first rule
When the user asks for "contacts at companies matching X," always discover the company set first, then find people at each company. Reasoning:
- Broad people-search queries return noisy results when the company filter is weak.
- A two-step (companies → people) flow lets you cap the per-company contact count (e.g. 3 prospects per account) cleanly.
- Per-company contact searches parallelize naturally via
action execute-batch— fan out onesearchLeadsper company in the source set.
Provider strengths at a glance
| Provider | Best for | Cost (credits) |
|---|---|---|
| salesNavigator | At-scale lead/account search, LinkedIn-native filters | 0.02 (lead) / 0.05 (account) |
| peopleDataLabs | Structured queries (queryPeople / queryCompanies), heavy filtering, backfill when other sources miss | 3 (flat) |
| theirStack | Tech-stack signals, jobs-posted signals, "everyone hiring for role X" | 0.5 |
| cargo native | matchBusiness / matchProspect for dedup; fetchProspects / fetchBusinesses for catalog browsing | 0.5 |
| icypeas | Cheapest people/company find when minimal filters work | 0.02 |
| firecrawl | Web search + scrape when no structured provider has the data | 0.05 |
| serper | Google Maps-style search for local SMBs / storefronts | 1 |
| theSwarm | Warm-intro paths to a target account or contact | 2 |
| snitcher | Anonymous website visitor identification | 0 (free credits-tier) |
For full provider details, see the per-provider playbooks under ../provider-playbooks/.
Cheapest path patterns
Pattern A — TAM list at scale (>500 companies)
# Source — cheapest large-scale account search
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchAccounts","config":{}}' \
--records '[{"filters":{"industry":["fintech"],"countries":["US"],"sizeMin":50,"sizeMax":500}}]' \
--wait-until-finishedIf salesNavigator filters don't cover the criteria you need, fall back to peopleDataLabs. Use searchCompanies (3) when criteria fit cargo's {conjonction, groups, conditions} filter shape; drop to queryCompanies (3) when you need a PDL SQL query (required for array-membership like investor name).
Pattern B — Contact discovery at known companies
# Fan out one searchLeads per company
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchLeads","config":{}}' \
--records "$(jq -c '[.companies[] | {filters:{accountId:.linkedinId,titles:["CTO","VP Engineering"]}}]' /tmp/companies.json)" \
--wait-until-finishedCap titles tightly — broad title filters dilute results.
Pattern C — Domain → company detail
When you already have a domain list and need firmographics:
# Match against cargo's catalog first (cheapest, most reliable for known companies)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records '[{"domain":"acme.com"},{"domain":"globex.com"}]' \
--wait-until-finished
# Then enrich the matches
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFirmographics","config":{}}' \
--records '<matched output from previous step>' \
--wait-until-finishedPattern D — Tech-stack-driven sourcing
# Find companies running a specific stack
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"theirStack","actionSlug":"searchCompanies","config":{}}' \
--data '{"technologies":["snowflake","dbt"],"locations":["United States"]}' \
--wait-until-finished
# Or "everyone hiring for role X" (intent signal)
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"theirStack","actionSlug":"searchJobs","config":{}}' \
--data '{"job_titles":["Head of RevOps"],"posted_at_max_age_days":30}' \
--wait-until-finishedPattern E — Investor portfolio sourcing
# Step 1 — query companies by investor (peopleDataLabs is the reliable source)
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"queryCompanies","config":{}}' \
--data '{"query":"SELECT * FROM company WHERE summary.investors LIKE %Sequoia%"}' \
--wait-until-finished > /tmp/portfolio.json
# Step 2 — fan out searchLeads per portfolio company (Pattern B above)Parallel execution
For any source list with >10 items, use action execute-batch with --records. The platform fans out automatically and respects rate limits per provider. For very large runs (>500), pass via batch create --workflow-uuid with a saved tool — see `../../cargo-orchestration/references/polling.md` for polling strategies.
When the cheapest source returns garbage
Two failure modes for sourcing:
1. Filter mismatch — provider doesn't expose the filter you need (e.g. salesNavigator can't filter by funding round). Move to a richer provider (peopleDataLabs.queryCompanies). 2. Coverage gap — provider doesn't have data for the niche (e.g. local SMBs aren't well-covered by salesNavigator). Move to a niche provider (serper.searchPlaces for SMBs, theirStack.searchCompanies for tech-driven).
If the user's request can't be served at all, file a workspaceManagement report describing the gap.
Writing outreach
How to use Cargo's LLM providers and AI agent surface to score, qualify, and personalize outreach. Covers provider routing, prompt patterns, and integration with sequencers.
LLM provider routing
Cargo exposes five LLM providers as kind: "connector" actions with credits-based pricing. All expose a single instruct action that takes a prompt + model and returns text.
| Provider | Strengths | Cost (credits, cheapest model) |
|---|---|---|
| anthropic | High-quality reasoning, long context, structured output via JSON mode | Haiku: 0.2 / Sonnet: 2 |
| openAi | Broadest model selection (gpt-4o, gpt-4o-mini), tool-use friendly | Mini: 0.006 / 4o: 0.03 |
| perplexity | Web-grounded research with citations | Sonar: 0.3 / Sonar-pro: 1 |
| gemini | Cheapest large-context option | Flash: 0.01 |
| deepSeek | Lowest-cost reasoning when latency isn't critical | varies |
For most outreach tasks: anthropic Haiku (0.2) is the right default. For deep research with citations: perplexity sonar-pro. For batch personalization on a large list: openAi gpt-4o-mini (0.006 — 30× cheaper than Haiku).
Prompt patterns
Lead scoring
You are an ICP fit scorer. Given a company profile, return a JSON object:
{
"score": <integer 0-10>,
"reasoning": "<one sentence>",
"qualified": <true|false>
}
Company profile:
- Domain: {domain}
- Industry: {industry}
- Employee count: {employee_count}
- Tech stack: {technographics}
- Recent funding: {funding}
ICP criteria: {icp_description}Use anthropic Haiku with output: {"type": "jsonSchema", "jsonSchema": {...}} to enforce structured output.
Personalization (one-paragraph opener)
Write a single short paragraph (≤ 60 words) opening a cold email to {first_name},
{title} at {company}. Reference the most relevant signal from the company profile
below. Sound like a peer, not a vendor. No "I hope this finds you well."
Company profile: {firmographics}
Recent signals: {signals}
ICP angle: {icp_angle}Run with openAi gpt-4o-mini for batch jobs (cheap, fast). Inputs come from earlier enrichment passes — keep the prompt short to amortize cost.
Qualification rubric
Return PASS or FAIL with a one-sentence reason.
Criteria (ALL must hold):
1. Company has 50–500 employees.
2. Company is in {target_industries}.
3. Company has at least one {target_role} on the team.
4. Company shows recent intent: hiring for {target_intent_role} OR using {target_tech} OR raised funding in last 12 months.Multi-pass pipeline (research → score → personalize)
Run as three sequential action execute-batch calls, piping each step's output into the next:
# Pass 1 — Research (perplexity for fresh web context)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"perplexity","actionSlug":"instruct","config":{}}' \
--records '[{"prompt":"What is <company> known for? 2-sentence summary.","model":"sonar"}, ...]' \
--wait-until-finished > /tmp/research.json
# Pass 2 — Score (anthropic with structured output)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"anthropic","actionSlug":"instruct","config":{}}' \
--records '<scoring inputs combining enrichment + research>' \
--wait-until-finished > /tmp/scores.json
# Pass 3 — Personalize (openAi mini for cost)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"openAi","actionSlug":"instruct","config":{}}' \
--records '<personalization inputs for high-scored leads only>' \
--wait-until-finished > /tmp/openers.jsonFilter between passes — only run pass 3 on leads that scored above your threshold in pass 2. Saves credits.
Sequencer integration
Once leads are enriched, scored, and personalized, push to a sequencer:
| Provider | Action | Notes |
|---|---|---|
| lemlist | upsertLead | Maps name/email/company directly. Custom fields go in payload. Email finder + verifier built in. |
| lgm (LaGrowthMachine) | createLead | Audience-driven; create the lead and assign to an audience. |
| instantly | (CRUD) | Sequencing platform, use HTTP for direct API or check the instantlyV2 integration. |
| smartlead | (CRUD) | Similar to instantly. |
| outreach / salesloft | (CRUD) | Enterprise sequencers. CRM-style integrations. |
| heyReach | (CRUD) | LinkedIn-focused outbound. |
These are mostly free CRUD operations (no credits) — push the personalized list with action execute-batch and the sequencer handles the campaign.
CRM sync
If the user wants the enriched + scored data in their CRM:
| Provider | Action | Notes |
|---|---|---|
| hubspot | upsertRecords | Map cargo columns to HubSpot properties. enrollToSequence for sequence enrollment. |
| salesforce | (CRUD) | Lead / Contact / Account objects. |
| pipedrive | (CRUD) | Person / Organization / Deal objects. |
| attio | (CRUD) | Custom-object friendly. |
CRM CRUD is free (no credits). Compose ad hoc — discover actions via cargo-ai connection integration get <slug> and run via orchestration action execute-batch.
When to use Cargo AI agents instead of raw LLM instruct
Cargo's cargo-ai skill (capability layer) lets you create persistent agents with system prompts, tools, and memory. Use those when:
- The agent needs RAG (upload a PDF for grounded answers).
- You want multi-turn chat with persistent context.
- The same prompt runs hundreds of times — define an agent once, invoke many.
For one-shot scoring or personalization across a batch, raw instruct is simpler and cheaper.
See `../../cargo-ai/SKILL.md` for the agent surface.
Action shape rules
Same as everywhere else: kind: "connector" with integrationSlug + actionSlug + config: {}. No connectorUuid in config.
For LLM instruct actions, the model field is in the per-record data, not in config:
{
"kind": "connector",
"integrationSlug": "anthropic",
"actionSlug": "instruct",
"config": {}
}Per-record:
{
"prompt": "...",
"model": "claude-haiku-4-5-20251001",
"maxTokens": 500
}cargo (native)
Cargo's proprietary enrichment layer. 22 credits-based actions covering firmographics, technographics, funding, ratings, financial metrics, intent signals, and prospect details. Cheap (most actions 0.5–1 credit) and the canonical source for firmographic data on companies cargo's catalog already covers.
When to reach for cargo (vs other providers)
- ✅ Firmographics on a known company:
enrichBusinessFirmographics(0.5) — first stop, cheapest comprehensive firmographic action in catalog. - ✅ Dedup / canonical-id lookup:
matchBusiness/matchProspect(0.5) — get a stablebusiness_id/prospect_idfor downstream enrichment. - ✅ Multi-axis signals on the same company: when you want firmographics + funding + technographics + ratings, batch them all against the same
business_id— cheapest path. - ✅ Prospect catalog browsing:
fetchProspects/fetchBusinessesfor "give me companies in country X with size band Y" without going through a third party. - ❌ Discovery outside cargo's catalog: if
matchBusinessreturns no match, fall back towaterfall.enrichCompany(1 cred) orpeopleDataLabs.enrichCompany(3 cred).
Credits-based actions — Business
| Action | Cost | What it returns |
|---|---|---|
matchBusiness | 0.5 | Resolve a company (by name, domain) to a cargo business_id. Run this first for any business enrichment. |
enrichBusinessFirmographics | 0.5 | Industry, size, geo, founded year, headquarters, etc. |
enrichBusinessFinancialMetrics | 0.5 | Revenue band, growth, financial health markers. |
enrichBusinessFundingAndAcquisitions | 0.5 | Funding rounds, investors, M&A history. |
enrichBusinessTechnographics | 1 | Tech stack: languages, frameworks, SaaS apps, infrastructure. |
enrichBusinessChallenges | 1 | Stated business challenges scraped from public material. |
enrichBusinessCompetitiveLandscape | 1 | Named competitors. |
enrichBusinessLinkedinPosts | 2 | Recent LinkedIn posts from the company page. |
enrichBusinessRatingsByEmployees | 1 | Glassdoor-style employee ratings. |
enrichBusinessStrategicInsights | 1 | High-level strategic narrative. |
enrichBusinessWebsiteKeywords | 0.5 | Keywords scraped from the company website. |
enrichBusinessWebsiteChanges | 1 | Recent website / messaging changes. |
enrichBusinessWorkforceTrends | 1 | Headcount trend over time. |
fetchBusinesses | 0.5 | Catalog-style search by country_code, region_country_code, company_size, company_revenue, google_category, naics_category, linkedin. |
fetchBusinessEvents | 0.5 | Events on a known business (event_types, timestamp_from). Useful for "what happened at this company recently?" signal queries. |
fetchBusinessStatistics | 1 | Aggregated statistics across the catalog filter. |
Credits-based actions — Prospect
| Action | Cost | What it returns |
|---|---|---|
matchProspect | 0.5 | Resolve a person (by email, phone_number, full_name + company_name, linkedin) to a cargo prospect_id. Run this first for any person enrichment. |
enrichProspectDetails | 2 | Title, role, location, contact details. |
enrichProspectLinkedinProfile | 2 | Full LinkedIn profile snapshot (work history, education, skills). |
enrichProspectLinkedinPosts | 2 | Recent LinkedIn posts authored by the prospect. |
fetchProspects | 0.5 | Catalog-style search by business_id, job_level, job_department, job_title, geo. |
fetchProspectEvents | 0.5 | Events on a known prospect (event_types, timestamp_from). Job changes, posts, role changes. |
Patterns
Pattern A — Firmographics on a domain list (cheapest)
# Step 1 — match domains to business_ids
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records '[{"domain":"acme.com"},{"domain":"globex.com"}]' \
--wait-until-finished
# Step 2 — enrich firmographics on the matched IDs
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFirmographics","config":{}}' \
--records '[{"business_id":"<uuid>"},{"business_id":"<uuid>"}]' \
--wait-until-finishedPattern B — Multi-axis enrichment on a single company
Run several enrichBusiness* actions in parallel (each independent batch):
for slug in enrichBusinessFirmographics enrichBusinessFundingAndAcquisitions enrichBusinessTechnographics; do
cargo-ai orchestration action execute \
--action "$(jq -nc --arg s "$slug" '{kind:"connector",integrationSlug:"cargo",actionSlug:$s,config:{}}')" \
--data '{"business_id":"<uuid>"}' \
--wait-until-finished &
done
waitPattern C — Prospect events monitoring
# Get the last 30 days of events (job changes, posts) for a prospect
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"fetchProspectEvents","config":{}}' \
--data '{
"prospect_id":"<uuid>",
"event_types":["job_change","linkedin_post"],
"timestamp_from":"2026-03-27T00:00:00Z"
}' \
--wait-until-finishedCommon pitfalls
- Always run `matchBusiness` / `matchProspect` first. All other cargo enrichments take a
business_id/prospect_id, not a domain or email. Skipping the match step → "missing required field" errors. - `matchBusiness` won't match every domain. For generic names ("acme" without context), or very small / private companies, the match may return null. Use the fallback chain (waterfall → peopleDataLabs).
- Don't run all 12 enrich actions per company. Pick the 2–4 that map to your ICP signals. Running everything is 11 credits per company, mostly wasted.
- Cost adds up at scale. For 500 companies × 4 enrichments × 1 credit average = 2,000 credits. Sample on 10 first to validate the data is what you need before fanning out.
Action shape
{"kind":"connector","integrationSlug":"cargo","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config` — single workspace connector resolves automatically.
FullEnrich
Premium contact-detail provider. Four credits-based actions, all focused on filling email + phone + LinkedIn gaps. Higher cost than cheap email finders, but better hit rate, and the only provider in the priority stack that does reverse-email lookup.
Credits-based actions
| Action | Cost | Inputs | Use for |
|---|---|---|---|
findEmail | 1 | firstName, lastName, domainName, companyName, linkedinUrl | Default email finder in the priority stack. |
findPhone | 6 | firstName, lastName, domainName, companyName, linkedinUrl | Premium phone lookup. Escalate from prospeo.findPhone (3). |
findPhoneAndEmail | 7 | firstName, lastName, domainName, companyName, linkedinUrl | Combined call when both are needed and you'd otherwise pay 1+6=7 anyway. No discount over running both separately. |
reverseEmailLookup | 2 | email | Unique action. Email → LinkedIn URL + company info. |
What it's for
- ✅ Default email finder in the prospecting spine — better hit rate than cheap providers (
hunter/icypeasat 0.5 cred), worth the 2× cost when conversion matters. - ✅ Reverse-email lookup — given an email, retrieve LinkedIn + company. Critical for de-anonymizing email-only data sources.
- ✅ Phone lookup with multi-input flexibility — accepts any combination of name/domain/company/linkedin.
Patterns
Pattern A — Default email finder in the spine
# After sourcing + (optional) basic enrichment, find emails for the contacts
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"findEmail","config":{}}' \
--records '[
{"firstName":"Alice","lastName":"Smith","domainName":"acme.com"},
{"firstName":"Bob","lastName":"Jones","linkedinUrl":"https://linkedin.com/in/bobjones"}
]' \
--wait-until-finishedPass either domainName (highest reliability) or linkedinUrl. Both is best.
Pattern B — Reverse lookup from an email
When you have an email but no other identity (e.g., from snitcher.searchSessions or a webform):
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"reverseEmailLookup","config":{}}' \
--records '[{"email":"alice@acme.com"},{"email":"bob@globex.com"}]' \
--wait-until-finishedReturns LinkedIn URL + company name + (sometimes) title. Feed the LinkedIn URL into linkedin.enrichProfile for full validation per the linkedin-url-lookup recipe.
Pattern C — Combined phone + email
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"findPhoneAndEmail","config":{}}' \
--records '[{"firstName":"Alice","lastName":"Smith","linkedinUrl":"…","domainName":"acme.com"}]' \
--wait-until-finishedCost is 7 credits — same as running findEmail (1) + findPhone (6) separately. Only use the combined call when API simplicity matters more than the ability to skip phone lookup for low-value rows.
Common pitfalls
- `findPhoneAndEmail` is not a discount. 7 credits = 1 (email) + 6 (phone). Run separately if you want to skip phone lookups for unqualified leads.
- Multi-input matters. Hit rate jumps significantly when you pass
linkedinUrlANDdomainNametogether vs. either alone. If you have both, use both. - Don't use `findEmail` for verification. It returns a single best-guess email; some are catch-all and will bounce. Always verify with
waterfall.verifyEmail(0.1 cred) before using in outreach.
Fallback chain
If FullEnrich.findEmail returns nothing for a row, escalate via:
1. peopleDataLabs.enrichPerson (3 cred) — heavyweight backfill. 2. Or hunter.findEmail (0.5 cred) — different underlying source, sometimes finds what FullEnrich misses. 3. Last resort: icypeas.findEmail (0.1 cred).
Don't run all four blindly — the spine is FullEnrich first, escalate only on misses.
Action shape
{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`. Note the capitalization: FullEnrich (camel-case starting with capital F).
peopleDataLabs (People Data Labs)
Heavyweight people / company database. Six credits-based actions, all flat 3 credits each. Use as backfill when cheaper sources miss, or as the primary source when you need query power salesNavigator's filters can't express.
Two filter shapes — pick the right one:
- `searchPeople` / `searchCompanies` use cargo's standard segment-filter shape:
{filter: {conjonction, groups: [{conjonction, conditions: [{propertyName, operator, value}, ...]}]}}. Operators:is,isNot,contains,notContains,lowerThan,lowerThanOrEquals,greaterThan,greaterThanOrEquals. Use when criteria are simple key/operator/value AND/OR combinations. - `queryPeople` / `queryCompanies` take a SQL string — PDL's SQL API. Use when you need joins, OR-of-AND combinations beyond what the cargo filter shape supports cleanly, or when you already have a SQL query from PDL's documentation. NOT Elasticsearch — it's PDL SQL.
Credits-based actions
| Action | Cost | Inputs | Use for |
|---|---|---|---|
searchPeople | 3 | filter, limit, pretty, titlecase | People search with cargo's {conjonction, groups, conditions} filter shape. |
searchCompanies | 3 | filter, limit, pretty, titlecase | Company search with the same cargo filter shape. |
queryPeople | 3 | query: <SQL string>, limit, pretty, titlecase | People search via PDL SQL query. Required for joins / complex bool combinations. |
queryCompanies | 3 | query: <SQL string>, limit, pretty, titlecase | Company search via PDL SQL query. Best for investor / funding / complex-filter sourcing. |
enrichPerson | 3 | parameters, options | Fill missing person fields. Default backfill when cargo + waterfall miss. |
enrichCompany | 3 | parameters, options | Fill missing company fields. Default backfill when cargo + waterfall miss. |
When to use peopleDataLabs (vs the alternatives)
- ✅ Investor / funding / VC-portfolio sourcing:
queryCompaniesSQL with aWHEREclause on PDL's investor / funding fields — salesNavigator can't express this. - ✅ Complex multi-axis filters that salesNavigator's UI-style filters can't combine: e.g., "fintech in EMEA AND Series B+ AND > 100 engineers AND running Snowflake".
- ✅ Heavyweight backfill: after
cargo.enrichPerson/Companyandwaterfall.enrich*both return empty, peopleDataLabs is the deepest source in the catalog. - ❌ Cheap at-scale sourcing: 3 cred is 60–150× more expensive than salesNavigator (0.02–0.05). Don't default here for volume work.
Patterns
Pattern A — Investor portfolio sourcing (queryCompanies, SQL)
queryCompanies accepts a SQL string (PDL's SQL API). Use it when criteria don't fit cargo's {conjonction, groups, conditions} shape — typically anything involving array containment (e.g., "investors includes X") or complex OR-of-AND combinations.
# "Find every company backed by Sequoia Capital, USA, 50-500 employees"
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"queryCompanies","config":{}}' \
--data '{
"query": "SELECT * FROM company WHERE summary.investors LIKE %Sequoia Capital% AND employee_count >= 50 AND employee_count <= 500 AND location.country = '\''united states'\''",
"limit": 200
}' \
--wait-until-finishedCommon PDL SQL fields: industry, employee_count, founded, total_funding_raised, summary.investors, location.country, location.locality, tags. See PDL's SQL reference for the full schema; cargo passes the SQL through verbatim.
Pattern B — Backfill missing person details
After cargo + waterfall both return empty for a row:
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"enrichPerson","config":{}}' \
--records '[
{"parameters":{"email":"alice@acme.com"}},
{"parameters":{"linkedin":"linkedin.com/in/alicesmith"}},
{"parameters":{"first_name":"Alice","last_name":"Smith","company":"Acme"}}
]' \
--wait-until-finishedparameters accepts any combination — email, linkedin, phone, first_name + last_name + company, first_name + last_name + location, etc. More identifiers = higher hit rate.
Pattern C — Structured people search via cargo's filter shape
For criteria that fit cargo's standard filter shape (key/operator/value AND/OR), prefer searchPeople over queryPeople:
# "Find Heads of Engineering at fintechs in NYC, 50-500 employees"
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"searchPeople","config":{}}' \
--data '{
"filter": {
"conjonction": "and",
"groups": [{
"conjonction": "and",
"conditions": [
{"propertyName": "job_title", "operator": "contains", "value": "head of engineering"},
{"propertyName": "job_company_industry", "operator": "is", "value": "financial services"},
{"propertyName": "location_locality", "operator": "is", "value": "new york"},
{"propertyName": "job_company_size", "operator": "greaterThanOrEquals", "value": 50},
{"propertyName": "job_company_size", "operator": "lowerThanOrEquals", "value": 500}
]
}]
},
"limit": 100
}' \
--wait-until-finishedNote the spelling: conjonction (with two os, no u) — same intentional cargo-platform-wide convention. Typo here fails silently with empty results.
If cargo's filter shape can't express the criteria (e.g., array-membership filters like summary.investors LIKE %X%), drop down to queryPeople SQL.
Common pitfalls
- 3 credits adds up fast. 1,000 enriches = 3,000 credits. Always run cargo + waterfall first; only escalate the ~20-30% of rows that those miss.
- `searchPeople` vs `queryPeople` — both cost 3 credits; pick by filter shape.
searchPeopleaccepts cargo's{conjonction, groups, conditions}(good for simple AND/OR criteria).queryPeopleaccepts a PDL SQL string (good for array-membership, joins, complex bool). Default tosearchPeople; drop down toqueryPeopleonly when SQL is required. - `titlecase: true` normalizes name capitalization in the response. Default is true; rarely worth disabling.
- `pretty: true` formats JSON for readability. Disable in production calls — adds bytes without value.
- Multi-axis matches dilute precision. Adding a 5th filter can reduce result quality (PDL's matching is forgiving when it has to be). Sample 10 results before fanning out.
Action shape
{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`.
Where peopleDataLabs sits in the spine
- Step 1 (SOURCE): only when salesNavigator's filters miss your criteria (e.g., funding-round filter).
- Steps 3–4 (ENRICH / SIGNAL): fallback after cargo + waterfall return empty.
- Step 7 (BACKFILL): canonical last-resort for missing emails / details.
Never the first stop unless the filter shape demands it.
salesNavigator (Sales Navigator)
LinkedIn-anchored search for accounts and leads. Cheapest sourcing in the cargo catalog — searchLeads at 0.02 credits/record and searchAccounts at 0.05 credits/record. Default for any at-scale list-building.
Credits-based actions
| Action | Cost | Inputs | Use for |
|---|---|---|---|
searchLeads | 0.02 | keywords, company, role, personal, recentUpdates, identityIds, limit | At-scale lead search by company / title / keywords. Cheapest at-scale people sourcing in catalog. |
searchAccounts | 0.05 | companyHeadcounts, headquarterLocationIds, industryCodes, numOfFollowers, … | At-scale account search by industry / size / geo. Cheapest at-scale company sourcing in catalog. |
extractLeadSearch | 0.02 | url, identityIds, limit | Extract leads from a saved Sales Navigator search URL. |
extractAccountSearch | 0.05 | url, identityIds, limit | Extract accounts from a saved Sales Navigator search URL. |
findCompanyInsights | 0.25 | companyId | Pull insights about a known LinkedIn company. |
findCompanyMetrics | 0.25 | companyId, parameters | Pull metrics about a known LinkedIn company. |
findEmployeesCount | 0.25 | companyId | Get employee count snapshot. |
findEmployeesDistribution | 0.25 | companyId | Get employee role/department distribution. |
searchLeadsLegacy | 6 | (deprecated) | Avoid. 300× more expensive than searchLeads. Only use if searchLeads is missing a filter you need (rarely). |
What it's for
- Default sourcing path for anything LinkedIn-shaped (industry, headcount, role, geo, posted updates).
- Cheap volume: build a 5,000-company TAM for ~250 credits.
- LinkedIn IDs: returned account/lead IDs slot directly into other LinkedIn-aware actions (cargo
enrichBusinessLinkedinPosts,theSwarm.searchWarmIntros…, downstream LinkedIn-anchored find/enrich).
Common pitfalls
- Don't use `searchLeadsLegacy` unless
searchLeadsliterally cannot express your filter. The cost difference is enormous. - `identityIds` filter scopes the search to specific LinkedIn member identities. Useful for "find leads currently or recently at company X" — combine with
companyfilter. - `recentUpdates: true` narrows to leads who posted recently, useful for warm-outreach signal but reduces volume.
- Pagination: results are paginated.
limitcaps a single call; for large pulls, iterate with the cursor returned in the response.
Sample payloads
Account search — 100 fintech companies in US, 50–500 headcount
{
"kind": "connector",
"integrationSlug": "salesNavigator",
"actionSlug": "searchAccounts",
"config": {}
}Per-record --data:
{
"companyHeadcounts": ["B", "C", "D"],
"industryCodes": [43],
"headquarterLocationIds": [103644278],
"limit": 100
}(Headcount enums and industry/location IDs are LinkedIn's internal codes — use connection integration get salesNavigator to inspect the autocomplete schema.)
Lead search — CTOs at a known account
{
"company": ["acme-inc"],
"role": {"function": [13], "seniority": [5, 7]},
"limit": 5
}Extract from a saved search URL
{
"url": "https://www.linkedin.com/sales/search/people?savedSearchId=…",
"limit": 1000
}Fallback chain
If salesNavigator.searchAccounts doesn't have your filter (e.g., you need to filter by investor or funding round → not in salesNavigator), escalate to peopleDataLabs.queryCompanies (3 credits, PDL SQL query). Never escalate to searchLeadsLegacy.
For people search niches salesNavigator misses:
- Local SMBs:
serper.searchPlaces(Google Maps). - Tech-stack-driven:
theirStack.searchCompanies. - Very specific role + industry combos with low LinkedIn coverage:
peopleDataLabs.searchPeople.
theirStack (Their Stack)
Tech-stack and jobs-posted intent signals. Three credits-based actions, all 0.5 credits each, covering the "find companies by what they use or what they're hiring for" pattern. Cargo's primary intent-signal provider for technographics-driven outreach.
Credits-based actions
| Action | Cost | Inputs | Use for |
|---|---|---|---|
searchTechnologies | 0.5 | fields, limit | Find what technologies a set of companies / domains uses. |
searchJobs | 0.5 | fields, companyFields, limit | Find currently-posted jobs matching role / location / company filters. Hiring-intent signal. |
searchCompanies | 0.5 | fields, jobFields, techFields, limit | Find companies by tech stack and/or job-posting filters combined. |
What it's for
- ✅ "Everyone hiring for role X" —
searchJobswith title and posting-window filters → list of companies actively recruiting that role. - ✅ "Companies running tech stack Y" —
searchTechnologieswith stack filter → list of companies using a specific framework, infra, or SaaS. - ✅ Combined intent + tech-stack —
searchCompanieswith bothjobFieldsandtechFields→ companies running stack Y AND hiring for role X. - ❌ Generic firmographic search — for "fintech in US, 50-500 headcount" without intent signals, salesNavigator (0.05) is 10× cheaper. Use theirStack only when the intent signal is the primary filter.
Patterns
Pattern A — Hiring-intent sourcing
# "Find every company hiring a Head of RevOps in the last 30 days"
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"theirStack","actionSlug":"searchJobs","config":{}}' \
--data '{
"fields": {
"job_titles": ["Head of RevOps", "VP RevOps", "Director of RevOps"],
"posted_at_max_age_days": 30,
"locations": ["United States"]
},
"companyFields": {
"employeeCounts": ["50-200", "200-500"]
},
"limit": 200
}' \
--wait-until-finishedResult includes both job postings and the companies that posted them. Dedup on company to get the unique account list.
Pattern B — Tech-stack-driven sourcing
# "Find every company using Snowflake AND dbt"
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"theirStack","actionSlug":"searchCompanies","config":{}}' \
--data '{
"techFields": {
"technologies": ["snowflake", "dbt"]
},
"fields": {
"industries": ["software", "saas"],
"headcountMin": 100
},
"limit": 500
}' \
--wait-until-finishedPattern C — Combined "hiring AND running stack"
# "Find every B2B SaaS hiring a data engineer AND already using Snowflake"
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"theirStack","actionSlug":"searchCompanies","config":{}}' \
--data '{
"techFields": {"technologies": ["snowflake"]},
"jobFields": {"job_titles": ["Data Engineer"], "posted_at_max_age_days": 60},
"fields": {"industries": ["software"]},
"limit": 200
}' \
--wait-until-finishedThis is the unique strength of theirStack — combined tech-stack AND hiring-intent in one call.
Common pitfalls
- `searchTechnologies` returns technology metadata, not company lists. Use it to discover canonical technology slugs, then plug those into
searchCompanies.techFields.technologies. - Don't over-filter. Combining 5+ filters can collapse the result set to zero. Start broad (1–2 filters), inspect counts, then narrow.
- Posting-window matters.
posted_at_max_age_daysdefaults loose; for "currently hiring" intent, use 30 or 60 days.
Action shape
{"kind":"connector","integrationSlug":"theirStack","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`.
When to combine with cargo native
After sourcing with theirStack, enrich with cargo rather than running theirStack on every record. The pattern:
1. theirStack.searchCompanies → 500 companies matching intent (250 credits). 2. cargo.matchBusiness → match each to cargo catalog (250 credits). 3. cargo.enrichBusinessFirmographics + cargo.enrichBusinessTechnographics → fill firmographic/tech detail (750 credits combined).
Total: ~1,250 credits for 500 fully-enriched companies with intent signal. Cheaper than running peopleDataLabs (3 credits/record × 3 actions = 4,500 credits).
waterfall (Waterfall.io)
Multi-source enrichment with built-in fallback across multiple underlying providers. Swiss-army-knife of the priority stack — one provider covering contact enrichment, company enrichment, email verification, phone lookup, prospect search, and the only credits-based job-change detection action in the catalog.
Credits-based actions
| Action | Cost | Inputs | Use for |
|---|---|---|---|
verifyEmail | 0.1 | email | Email verification. Cheapest tier in the priority stack. |
enrichCompany | 1 | linkedin, domain, name | Fallback for unmatched cargo companies; also useful when LinkedIn is the only known identifier. |
enrichContact | 2 | linkedin, full_name, first_name, last_name, domain, email, include_extra_fields | Multi-source contact enrichment. |
detectJobChange | 3 | professional_email, personal_email, company_domain, company_linkedin, contact_linkedin | Unique action. Returns MOVED / LEFT / NO_CHANGE / UNKNOWN plus updated person info. |
searchProspects | 3 | domain, company_name, linkedin, title_filter, location_country, … | People search; alternative to salesNavigator when LinkedIn-anchored search isn't enough. |
findPhone | 7 | linkedin, full_name, first_name, last_name, domain, email, include_extra_fields | Phone number lookup. Premium pricing — escalate from prospeo.findPhone (3) only when needed. |
What it's for
- ✅ Email verification at the cheapest tier (0.1) — default for any verify step in the spine.
- ✅ Job change signal —
detectJobChangeis the only credits-based action of its kind in the entire 120-integration catalog. Cargo-unique strength. - ✅ Fallback contact / company enrichment — when cargo native + FullEnrich miss, waterfall is the next stop before the heavyweight peopleDataLabs.
- ✅ Multi-identifier enrichment — accepts LinkedIn URL, domain, name, or email. Useful when the input is weakly identified.
Patterns
Pattern A — Email verification at scale
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"verifyEmail","config":{}}' \
--records '[{"email":"alice@acme.com"},{"email":"bob@globex.com"}, ...]' \
--wait-until-finishedAt 0.1 cred/email, 1,000 emails = 100 credits. Default verify step in any prospecting pipeline.
Pattern B — Job change detection (signal segment)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"detectJobChange","config":{}}' \
--records '[
{"professional_email":"alice@acme.com","contact_linkedin":"https://linkedin.com/in/alicesmith"},
{"professional_email":"bob@globex.com","contact_linkedin":"https://linkedin.com/in/bobjones"},
...
]' \
--wait-until-finishedPass any combination of identifiers; multi-identifier inputs improve coverage. Result statuses:
MOVED— person changed company; new role + company returned.LEFT— person left and current state unknown.NO_CHANGE— same role / company.UNKNOWN— no signal available.
Filter to MOVED for outbound timing. See `../recipes/job-change-monitoring.md` for the full pattern including segment write-back.
Pattern C — Fallback contact enrichment
# Only run on rows where cargo.matchProspect / enrichProspectDetails returned no data
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"enrichContact","config":{}}' \
--records '[
{"linkedin":"https://linkedin.com/in/alice","include_extra_fields":true},
{"first_name":"Bob","last_name":"Jones","domain":"globex.com"}
]' \
--wait-until-finishedinclude_extra_fields: true increases response richness but doesn't change cost.
Common pitfalls
- Don't use `findPhone` first. At 7 credits, it's the most expensive phone action in the priority stack. Try
prospeo.findPhone(3) first; escalate to waterfall only when prospeo misses. - `detectJobChange` requires at least one identifier. Best coverage: LinkedIn URL + company domain. Email-only inputs often return UNKNOWN.
- `searchProspects` is 3 credits/record — comparable to peopleDataLabs but with less rich filtering. Default to salesNavigator.searchLeads (0.02) unless you need waterfall's specific filter combinations.
Action shape
{"kind":"connector","integrationSlug":"waterfall","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`.
Recipe — Find expansion contacts inside customer accounts
Use this recipe when the user wants to multi-thread existing customers — find additional buyers, champions, or budget-holders within accounts they already sell to. The output is a per-customer list of net-new contacts (not already in the workspace's Contacts model) at target personas, ready for hand-off to outreach.
Trigger phrases:
- "Find me other buyers at our existing customer accounts."
- "Who should we be talking to for upsell at our customers?"
- "Multi-thread the champion accounts — who else matters?"
- "Find net-new contacts at customer X."
Why this recipe exists
Expansion revenue typically beats new-logo revenue on CAC by 3–5×. The blocker is rarely which accounts (the CRM already knows the customer list) — it's which net-new contacts at those accounts to engage. This recipe mechanizes the discovery: pull customer accounts, search for additional personas, deduplicate against contacts already in the CRM, enrich, ready for outreach.
The cargo-unique piece is the dedup against the workspace's Contacts model — sourcing tools (salesNavigator, peopleDataLabs) don't know who you already have in HubSpot/Salesforce.
Recipe
Step 1 — Pull the customer-account list
cargo-ai storage model list # find Companies + Contacts model UUIDs
COMPANIES_MODEL=...
CONTACTS_MODEL=...
cargo-ai segmentation segment fetch \
--model-uuid "$COMPANIES_MODEL" \
--filter '{"conjonction":"and","groups":[{"conjonction":"and","conditions":[
{"kind":"string","columnSlug":"lifecycle_stage","operator":"is","values":["customer"]},
{"kind":"string","columnSlug":"subscription_status","operator":"is","values":["active"]}
]}]}' > /tmp/customers.jsonAdjust filters to scope: top-tier customers only, customers in renewal window (next 90d), customers with NRR > 100%, etc.
Step 2 — Search for additional personas at each customer
Choose precision (salesNavigator) or scale (peopleDataLabs). For expansion, precision usually wins — you only need 2–4 net-new contacts per account, and signal quality matters more than volume:
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchLeads","config":{}}' \
--records "$(jq -c '[.records[] | {
company_domain: .domain,
title_keywords: ["VP Engineering","Director of Data","Head of Analytics","CTO"],
function: ["Engineering","Data","Product"]
}]' /tmp/customers.json)" \
--wait-until-finished > /tmp/expansion-candidates.jsonCustomize title_keywords and function to match the expansion motion — different from the original champion persona. If the original buyer was VP Sales, expansion personas might be VP Marketing, Head of Customer Success, Head of Engineering, etc.
For scale (e.g. when expanding to 1,000+ customer accounts at once), swap to peopleDataLabs.searchLeads — cheaper per record, broader coverage, lower signal-to-noise.
Step 3 — Pull existing contacts at the same accounts
cargo-ai segmentation segment fetch \
--model-uuid "$CONTACTS_MODEL" \
--filter "$(jq -c '{
conjonction: "and",
groups: [{
conjonction: "and",
conditions: [{
kind: "string",
columnSlug: "company_domain",
operator: "in",
values: [.records[].domain]
}]
}]
}' /tmp/customers.json)" > /tmp/existing-contacts.jsonStep 4 — Deduplicate: keep only net-new candidates
# Build a set of known contact identifiers (LinkedIn URL + email)
jq -r '.records[] | (.linkedin_url // "") + "|" + (.email // "")' /tmp/existing-contacts.json | sort -u > /tmp/known.txt
# Filter expansion candidates against the known set
jq -c '[.results[] | . as $c
| (($c.linkedin_url // "") + "|" + ($c.email // "")) as $id
| select($id | IN($ARGS.positional[]) | not)
| $c
]' --args $(cat /tmp/known.txt) /tmp/expansion-candidates.json > /tmp/net-new.jsonLinkedIn URL is the most reliable dedup key — emails can vary (work vs. personal), names collide.
Step 5 — Enrich the net-new contacts
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"enrichProspectDetails","config":{}}' \
--records "$(jq -c '[.[] | {
first_name, last_name,
company_domain,
contact_linkedin: .linkedin_url
}]' /tmp/net-new.json)" \
--wait-until-finished > /tmp/enriched.jsonFor mobile direct dials and top-tier accuracy (worth it on a small high-value expansion list), swap in FullEnrich.enrichPerson.
Step 6 — Tag with expansion signal, hand off to outreach
jq -c '[.results[] | . + {
signal_summary: ("Expansion — your colleague <existing champion at " + .company_name + "> is already a customer; reaching out to introduce the same value to your function.")
}]' /tmp/enriched.json > /tmp/expansion-ready.jsonThe expansion signal in the personalization prompt produces qualitatively different cold copy than a cold-prospect signal — name-drop the existing user, tie value to the recipient's function. Pass to `outreach-activation.md` from Step 5 onwards (skip its enrichment step — already done).
Recurring expansion (cron / play)
For continuous multi-threading:
1. Trigger: monthly cron. 2. Source: customer-accounts segment. 3. Nodes: searchLeads → fetch existing Contacts → dedup → enrich → write to "Expansion candidates" segment. 4. Downstream: a separate play takes new members of "Expansion candidates" and triggers outreach-activation.
For play setup, see `../../cargo-orchestration/references/examples/plays.md`.
Credit budget
For 50 top-tier customer accounts, expanded monthly:
| Step | Per record | 50 accounts (3 candidates each = 150) |
|---|---|---|
salesNavigator.searchLeads | 2 | 100 (per account) |
waterfall.enrichProspectDetails | 1 | 150 (per net-new) |
| Total monthly | — | 250 |
Expansion typically runs on smaller targeted lists, so per-record costs (even high-precision providers like FullEnrich at 5 credits/record) stay affordable. The dedup step is free — it's a workspace storage query.
Action shape
Every action follows: {"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config` — see `../../cargo-orchestration/references/examples/actions.md`.
Output retrieval
For batch runs, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug>. See `../references/output-retrieval.md`.
Related
- `prospecting.md` — broader: net-new prospects across the TAM, not constrained to existing customers.
- `outreach-activation.md` — downstream: turns the expansion segment into send-ready outreach. The
signal_summarytag from Step 6 feeds directly into its personalization prompt.
Recipe — Build a TAM list
Use when: the user wants a Total Addressable Market list of companies (and optionally contacts at those companies) matching ICP criteria.
Trigger phrases:
- "Build me a TAM of fintech companies in the US, 50–500 employees."
- "Source 1,000 SaaS companies hiring data engineers."
- "Find every Series A-B startup running Snowflake."
- "Give me all the e-commerce brands in the EU under 100 people."
Sourcing decision tree
The right step-1 provider depends on which filter is primary:
| Primary filter | Provider | Cost (credits) | Notes |
|---|---|---|---|
| Industry / size / geo | salesNavigator.searchAccounts | 0.05 | LinkedIn-anchored. Default at-scale. |
| Funding stage / investor / round size | peopleDataLabs.queryCompanies | 3 | PDL SQL string. Required for array-membership filters like summary.investors LIKE %X%. |
| Tech stack | theirStack.searchCompanies (with techFields) | 0.5 | Tech-stack-driven sourcing. |
| Hiring for role X | theirStack.searchJobs | 0.5 | Hiring-intent signal. |
| Local SMBs / storefronts | serper.searchPlaces | 1 | Google Maps-style. |
| Already have a domain list | (skip sourcing) | — | Go straight to step 2 (dedup + enrich). |
For combined filters (e.g. fintech in US AND running Snowflake AND hiring data engineers), do parallel queries and intersect client-side.
Volume / cost guidance
| Target volume | Recommended sourcing path | Estimated credits (sourcing only) |
|---|---|---|
| 100 companies | salesNavigator.searchAccounts | ~5 |
| 500 companies | salesNavigator.searchAccounts | ~25 |
| 1,000 companies | salesNavigator.searchAccounts | ~50 |
| 5,000 companies | salesNavigator.searchAccounts (paginate) | ~250 |
| 10,000 companies | peopleDataLabs.queryCompanies (high-quality, structured) | ~30,000 (3/company) |
For 5,000+ companies, always sample 50 first to validate the data quality before paying for the full volume.
Inputs you need
- ICP criteria (industry, headcount range, geo, revenue band, funding stage, tech-stack signals — one or more).
- Target volume (10? 500? 5000? — drives provider choice).
- Whether contacts are required, and if so, role filter.
- Where the result lives (write to a Companies model? Export to CSV? Push to a CRM?).
If anything is missing, ask the user once before sourcing.
Recipe
Step 1 — Source companies
Cheapest at scale (≥ 100 companies): salesNavigator.searchAccounts (0.05 cred/company).
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchAccounts","config":{}}' \
--data '{
"filters": {
"industries": ["Financial Services"],
"countries": ["US"],
"headcountMin": 50,
"headcountMax": 500
},
"limit": 500
}' \
--wait-until-finished > /tmp/companies.jsonFilter mismatch? Fall back to peopleDataLabs. Pick the right action by filter shape:
- `searchCompanies` (cargo's
{conjonction, groups, conditions}filter shape) for simple AND/OR criteria:
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"searchCompanies","config":{}}' \
--data '{
"filter": {
"conjonction": "and",
"groups": [{
"conjonction": "and",
"conditions": [
{"propertyName": "industry", "operator": "is", "value": "financial services"},
{"propertyName": "employee_count", "operator": "greaterThanOrEquals", "value": 50},
{"propertyName": "employee_count", "operator": "lowerThanOrEquals", "value": 500},
{"propertyName": "location.country", "operator": "is", "value": "united states"}
]
}]
},
"limit": 500
}' \
--wait-until-finished > /tmp/companies.json- `queryCompanies` (PDL SQL string) when criteria require array-membership, joins, or complex bool combinations:
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"queryCompanies","config":{}}' \
--data '{
"query": "SELECT * FROM company WHERE industry = '\''financial services'\'' AND employee_count >= 50 AND employee_count <= 500 AND location.country = '\''united states'\''",
"limit": 500
}' \
--wait-until-finished > /tmp/companies.jsonStep 2 — Match against cargo's catalog (dedup + warm)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records "$(jq -c '[.companies[] | {domain: .website}]' /tmp/companies.json)" \
--wait-until-finished > /tmp/matched.jsonMatched rows now have a stable cargo businessUuid for downstream enrichment.
Step 3 — Enrich firmographics + signals
# Firmographics (cheap, comprehensive)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFirmographics","config":{}}' \
--records "$(jq -c '[.results[] | {businessUuid: .businessUuid}]' /tmp/matched.json)" \
--wait-until-finished > /tmp/firmo.json
# Funding signals (only worth running if funding is part of ICP)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFundingAndAcquisitions","config":{}}' \
--records "$(jq -c '[.results[] | {businessUuid: .businessUuid}]' /tmp/matched.json)" \
--wait-until-finished > /tmp/funding.json
# Tech-stack (only worth running if technographics are part of ICP)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessTechnographics","config":{}}' \
--records "$(jq -c '[.results[] | {businessUuid: .businessUuid}]' /tmp/matched.json)" \
--wait-until-finished > /tmp/tech.jsonIf a company didn't match in step 2, fall back to waterfall.enrichCompany (1 cred):
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"enrichCompany","config":{}}' \
--records '<unmatched rows>' \
--wait-until-finished > /tmp/firmo-fallback.jsonStep 4 — (Optional) Find contacts at each company
Only run if the user asked for contacts. Cap at 3-5 per company.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchLeads","config":{}}' \
--records "$(jq -c '[.results[] | {filters:{accountId: .linkedinId, titles:[\"CTO\",\"VP Engineering\"]}, limit: 5}]' /tmp/matched.json)" \
--wait-until-finished > /tmp/contacts.jsonStep 5 — (Optional) Find emails for the contacts
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"findEmail","config":{}}' \
--records "$(jq -c '[.contacts[] | {firstName:.firstName, lastName:.lastName, companyDomain:.companyDomain}]' /tmp/contacts.json)" \
--wait-until-finished > /tmp/emails.jsonStep 6 — Verify emails
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"verifyEmail","config":{}}' \
--records "$(jq -c '[.results[] | {email: .email}]' /tmp/emails.json)" \
--wait-until-finished > /tmp/verified.jsonStep 7 — Write to model / export / push to CRM
If a Companies model exists in the workspace, write back via cargo-ai storage column create patterns (see `../../cargo-storage/SKILL.md`).
For a CSV export, point the user at cargo-ai segmentation segment download (see `../../cargo-analytics/references/examples/exports.md`).
For CRM push, compose ad hoc with hubspot.upsertRecords / salesforce.upsert — discover the action via cargo-ai connection integration get hubspot (or salesforce) and run via orchestration action execute-batch.
Credit budget (rough)
For a 500-company TAM with contacts:
| Step | Per record | Records | Subtotal |
|---|---|---|---|
| 1. Source (salesNavigator.searchAccounts) | 0.05 | 500 | 25 |
| 2. matchBusiness | 0.5 | 500 | 250 |
| 3. enrichBusinessFirmographics | 0.5 | 500 | 250 |
| 3. enrichBusinessFundingAndAcquisitions (optional) | 0.5 | 500 | 250 |
| 3. enrichBusinessTechnographics (optional) | 1 | 500 | 500 |
| 4. searchLeads (3 contacts each) | 0.02 × 3 | 500 | 30 |
| 5. FullEnrich.findEmail | 1 | 1500 | 1500 |
| 6. waterfall.verifyEmail | 0.1 | 1500 | 150 |
Total: ~2,955 credits for 500 companies + 1,500 contacts (~6 credits per fully-enriched contact).
Cut steps the user doesn't need (skip step 3 funding/tech if not part of ICP, skip steps 4-6 if no contacts needed) to bring the cost down.
When to deviate
- User wants local SMBs / storefronts → use
serper.searchPlacesfor sourcing instead of salesNavigator. - User wants "everyone hiring for X role" → use
theirStack.searchJobsthen dedup to companies. - User wants investor-backed companies → start with
peopleDataLabs.queryCompanies(PDL SQL) filtering onsummary.investors LIKE %X%. See `portfolio-prospecting.md` for the full pattern.
For these patterns, see `tech-intent.md` and `portfolio-prospecting.md`.
Recipe — Track recently-funded companies for outbound timing
Use this recipe when the user wants to identify or monitor companies that recently raised funding. Funding events are one of the strongest outbound-timing signals — a fresh round means budget, hiring, and a willingness to evaluate new tools.
Trigger phrases:
- "Find every fintech that raised in the last 90 days."
- "Which of our target accounts just got funded?"
- "Alert me when a company in my segment raises Series B or later."
- "Build a 'recently funded' segment for outbound."
Recipe
Pattern A — Surface recent fundraises across a target segment
# 1. Pull the target accounts
cargo-ai storage model list # find the Companies model UUID
MODEL_UUID=...
cargo-ai segmentation segment fetch \
--model-uuid "$MODEL_UUID" \
--filter '{"conjonction":"and","groups":[{"conjonction":"and","conditions":[
{"kind":"string","columnSlug":"icp_tier","operator":"is","values":["tier-1","tier-2"]}
]}]}' > /tmp/targets.json
# 2. Match each domain to a cargo business_id (required for the funding action)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records "$(jq -c '[.records[] | {domain}]' /tmp/targets.json)" \
--wait-until-finished > /tmp/matched.json
# 3. Pull funding + acquisition events
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFundingAndAcquisitions","config":{}}' \
--records "$(jq -c '[.results[] | select(.business_id) | {business_id}]' /tmp/matched.json)" \
--wait-until-finished > /tmp/funding.json
# 4. Filter to recent rounds (last 90 days)
jq -c '[.results[]
| select(.funding_rounds[]? | (.announced_date // "") > "'$(date -v-90d -u +%Y-%m-%d 2>/dev/null || date -d "90 days ago" -u +%Y-%m-%d)'")]' \
/tmp/funding.json > /tmp/recent-funded.jsonPattern B — Monitor a known company for new events (event-driven)
# Get all events of type "funding" or "acquisition" for one or many businesses
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"fetchBusinessEvents","config":{}}' \
--records '[
{"business_id":"<uuid>","event_types":["funding","acquisition"],"timestamp_from":"2026-03-01T00:00:00Z"}
]' \
--wait-until-finishedfetchBusinessEvents returns events of various types — pass event_types to scope. Common types include funding, acquisition, hiring, linkedin_post. Useful for "what happened at this company recently?" queries beyond funding.
Pattern C — Recurring funding watch (play)
For continuous monitoring (e.g. daily scan of target accounts): 1. Trigger: daily cron. 2. Source: a saved segment of target accounts. 3. Action: cargo.enrichBusinessFundingAndAcquisitions or cargo.fetchBusinessEvents with timestamp_from = yesterday. 4. Output: write rows where new events found to a "Recently Funded" signal segment. 5. Optional: post Slack notification per new funding event.
For setting up a play / scheduled tool, see ../../cargo-orchestration/references/examples/plays.md.
Credit budget
| Pattern | Cost per record |
|---|---|
cargo.matchBusiness | 0.5 |
cargo.enrichBusinessFundingAndAcquisitions | 0.5 |
cargo.fetchBusinessEvents | 0.5 |
500 target accounts × (0.5 match + 0.5 funding) = 500 credits per scan. Daily cron over 30 days = 15,000 credits.
For long-running monitoring, prefer fetchBusinessEvents with timestamp_from set to "since last scan" — cheaper than re-pulling full funding history each time.
Surfacing the signal
The output of this recipe is a list of company records with funding events. Useful next steps:
- Outbound timing: hand the list to a sequencer (lemlist / lgm / instantly) for a fresh-funding-triggered campaign — discover the launch action via
cargo-ai connection integration get lemlistand run viaorchestration action execute-batch. - CRM enrichment: write a
last_funding_round_atcolumn on the Companies model, push to HubSpot viahubspot.upsertRecords(compose ad hoc — see `build-tam.md` for the CRM-push pattern). - Sales notification: post to Slack when a tier-1 account hits a funding milestone. Use
slackconnector orhttp.callfor webhook patterns.
Action shape
{"kind":"connector","integrationSlug":"cargo","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config` — single workspace cargo connector resolves automatically.
Output retrieval
For batch runs, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug>.
Alternative provider
enrichCrm.getFunding (1 credit) is an alternative if cargo's match misses a private company. Generally cargo native has wider coverage for venture-backed startups; escalate to enrichCrm for fallback only.
When stuck — file a workspace report
If a target company has known recent funding but cargo.enrichBusinessFundingAndAcquisitions returns empty: file a cargo-ai workspaceManagement report create with the domain so cargo can verify catalog coverage.
Recipe — Surface ICP signals from Closed-Won vs Closed-Lost
Use this recipe when the user wants to discover their real ICP from conversion data, not from gut feel. The recipe pulls Closed-Won and Closed-Lost segments via storage query execute, enriches both with the same firmographic and tech signals, and surfaces the features that differ most between them. Output: a ranked list of "high-fit signals" the user can use to filter prospecting.
Trigger phrases:
- "What does our ideal customer look like?"
- "Find the patterns in our Closed-Won deals."
- "Why do we win against some prospects and lose against others?"
- "What ICP signals should we be filtering on?"
Why this is its own skill
Most prospecting skills are forward-looking ("find me X" / "enrich Y"). ICP discovery is backward-looking — analyze what worked, then turn the patterns into filters. It exercises:
- Storage (
cargo-ai storage query execute) to pull Won/Lost segments. - Cargo native enrichments (
enrichBusinessFirmographics,…Technographics,…FundingAndAcquisitions) to fill comparison signals. - LLM analysis (
anthropic.instruct) to surface non-obvious patterns.
Recipe
Step 1 — Identify the deal model and pull segments
# Find the model holding deals (usually a Deals or Opportunities model in the workspace)
cargo-ai storage model list
cargo-ai storage dataset list
# Optional — fetch the DDL for column types and SQL dialect
cargo-ai storage model get-ddl <deals-model-uuid>Pull both segments via storage query execute (tables are referenced as <datasetSlug>.<modelSlug> and rewritten to the underlying storage table under the hood):
# Closed-Won deals + their associated companies
cargo-ai storage query execute "
SELECT d.uuid as deal_uuid, c.uuid as company_uuid, c.domain, c.name
FROM default.deals d
JOIN default.companies c ON d.company_uuid = c.uuid
WHERE d.stage = 'closed-won'
AND d.closed_at >= CURRENT_DATE - INTERVAL '12 months'
" > /tmp/won.json
# Closed-Lost deals + their associated companies
cargo-ai storage query execute "
SELECT d.uuid as deal_uuid, c.uuid as company_uuid, c.domain, c.name
FROM default.deals d
JOIN default.companies c ON d.company_uuid = c.uuid
WHERE d.stage = 'closed-lost'
AND d.closed_at >= CURRENT_DATE - INTERVAL '12 months'
" > /tmp/lost.json(Swap default for the user's dataset slug if it differs, and adjust the stage filter to match the user's pipeline stage labels.)
Step 2 — Match each company to cargo
for src in won lost; do
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records "$(jq -c '[.[] | {domain}]' /tmp/$src.json)" \
--wait-until-finished > /tmp/$src-matched.json
doneStep 3 — Enrich both segments with the SAME signals
Run the same enrichments on both segments so the diff is apples-to-apples:
for src in won lost; do
for action in enrichBusinessFirmographics enrichBusinessTechnographics enrichBusinessFundingAndAcquisitions enrichBusinessFinancialMetrics; do
cargo-ai orchestration action execute-batch \
--action "$(jq -nc --arg a "$action" '{kind:"connector",integrationSlug:"cargo",actionSlug:$a,config:{}}')" \
--records "$(jq -c '[.results[] | select(.business_id) | {business_id}]' /tmp/$src-matched.json)" \
--wait-until-finished > /tmp/$src-$action.json
done
doneStep 4 — Diff feature distributions
For each feature (industry, size band, tech, funding stage, …), compute the % of Won vs % of Lost showing that feature, then sort by absolute difference. Largest deltas = strongest ICP signals.
This is best done in Python or via anthropic.instruct with structured output:
# Concatenate enrichment results into a structured comparison input
jq -s '{
won: [.[0].results[], .[1].results[], .[2].results[], .[3].results[]] | group_by(.business_id) | map(reduce .[] as $r ({}; . * $r)),
lost: [.[4].results[], .[5].results[], .[6].results[], .[7].results[]] | group_by(.business_id) | map(reduce .[] as $r ({}; . * $r))
}' \
/tmp/won-enrichBusinessFirmographics.json \
/tmp/won-enrichBusinessTechnographics.json \
/tmp/won-enrichBusinessFundingAndAcquisitions.json \
/tmp/won-enrichBusinessFinancialMetrics.json \
/tmp/lost-enrichBusinessFirmographics.json \
/tmp/lost-enrichBusinessTechnographics.json \
/tmp/lost-enrichBusinessFundingAndAcquisitions.json \
/tmp/lost-enrichBusinessFinancialMetrics.json > /tmp/comparison.json
# Use anthropic to surface differentiating signals
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"anthropic","actionSlug":"instruct","config":{}}' \
--data '{
"model": "claude-sonnet-4-6",
"prompt": "Two arrays: Closed-Won companies and Closed-Lost companies. Compare feature distributions and surface the top 10 signals that differentiate Won from Lost. Return JSON: [{signal, won_rate, lost_rate, difference_pct, why_it_matters}]. Data: <paste /tmp/comparison.json>",
"output": {"type": "jsonSchema", "jsonSchema": {"type": "array", "items": {"type": "object"}}}
}' \
--wait-until-finishedFor deal sets > 100 records, do the diff in Python directly — LLM is more reliable for pattern interpretation on small samples than for aggregation on large ones.
Step 5 — Validate signals (optional)
For each surfaced signal, validate by running it as a filter against the Won segment and checking hit rate:
# Example: signal is "uses Snowflake" → query storage + cargo technographics
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"theirStack","actionSlug":"searchTechnologies","config":{}}' \
--data '{"fields":{"keywords":"snowflake"},"limit":1}' \
--wait-until-finishedIf the technology is in theirStack's catalog and the Won-rate is significantly higher than Lost-rate, lock the signal in for prospecting.
Step 6 — Encode signals as ICP filters
Take the top 5–10 signals and translate them into filter syntax for prospecting providers:
- Industry / size / geo →
salesNavigator.searchAccountsfilters. - Tech stack →
theirStack.searchCompanies.techFields.technologies. - Funding range →
peopleDataLabs.queryCompaniesES query.
Hand the encoded filters to cargo-tam-build to build the next prospecting list.
Credit budget
| Step | Cost per Won/Lost record | Records (assume 100 each) | Subtotal |
|---|---|---|---|
| matchBusiness | 0.5 | 200 | 100 |
| enrichBusinessFirmographics | 0.5 | 200 | 100 |
| enrichBusinessTechnographics | 1 | 200 | 200 |
| enrichBusinessFundingAndAcquisitions | 0.5 | 200 | 100 |
| enrichBusinessFinancialMetrics | 0.5 | 200 | 100 |
| anthropic.instruct (Sonnet, one call) | ~2 | 1 | 2 |
| Total | ~602 credits for full Won/Lost analysis on 200 deals |
The recipe is one-shot — run it once when the user wants to refine ICP, then use the output to drive prospecting going forward. Re-run quarterly to capture pipeline drift.
Required inputs
Before executing, the agent needs: 1. The Deals / Opportunities model UUID (from cargo-ai storage model list). 2. The closed-won / closed-lost stage labels in the user's pipeline (often closed-won / closed-lost but may be won / lost / unqualified, etc.). 3. The lookback window (default: 12 months).
If any are missing, ask once before running — don't guess on the SQL.
Action shape
{"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`.
Output retrieval
For batch enrichments, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug>.
Output deliverable
The recipe's final output is a markdown table the agent presents to the user:
Top differentiating ICP signals (Won vs Lost):
| # | Signal | Won rate | Lost rate | Δ | Notes |
|---|--------|---------:|----------:|--:|-------|
| 1 | Headcount 50-200 | 78% | 22% | +56pp | Smaller mid-market converts better |
| 2 | Uses Snowflake | 64% | 18% | +46pp | Data-mature stack signal |
| 3 | Series B+ | 71% | 35% | +36pp | Funded pipeline = budget |
| ... | | | | | |Plus a follow-up suggestion: "Want me to run /cargo-tam-build with these signals as the filter?"
When stuck — file a workspace report
If storage query execute fails or the deal model schema is unfamiliar, file via cargo-ai workspaceManagement report create. See `../../cargo-workspace-management/SKILL.md`.
Recipe — Detect job changes in a contact segment
Use this recipe when the user wants to detect job changes among a list of contacts. The only provider in cargo's 120-integration catalog with a credits-based job-change action is `waterfall.detectJobChange` — this recipe exists to make that capability discoverable and reusable.
Trigger phrases:
- "Has anyone in our customer list changed jobs?"
- "Show me which contacts in the New Inbound segment have moved companies."
- "Track job changes for our top accounts."
- "Find all our champions who left their company."
Why this recipe exists
Job change is one of the highest-intent signals in B2B GTM:
- MOVED contacts at target accounts → re-engage at the new company; the relationship is warm.
- MOVED contacts at customer accounts → renewal / churn risk; the original champion is gone.
- MOVED prospects in old segments → trigger fresh outreach; previous reasons not to buy may no longer apply.
waterfall.detectJobChange returns one of: MOVED, LEFT, NO_CHANGE, UNKNOWN — plus updated person info when MOVED.
Recipe
Step 1 — Pull the contact segment
cargo-ai storage model list # find the Contacts model UUID
MODEL_UUID=...
cargo-ai segmentation segment fetch \
--model-uuid "$MODEL_UUID" \
--filter '{"conjonction":"and","groups":[{"conjonction":"and","conditions":[
{"kind":"string","columnSlug":"lifecycle_stage","operator":"is","values":["customer","champion"]}
]}]}' > /tmp/contacts.jsonAdjust the filter to match the segment the user wants to monitor.
Step 2 — Detect job changes
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"detectJobChange","config":{}}' \
--records "$(jq -c '[.records[] | {
professional_email: .email,
contact_linkedin: .linkedin_url,
company_domain: .company_domain
}]' /tmp/contacts.json)" \
--wait-until-finished > /tmp/job-changes.jsonIdentifier strategy: pass as many identifiers as you have. Best coverage: contact_linkedin + company_domain. Email-only inputs often return UNKNOWN.
Step 3 — Filter to MOVED contacts
jq -c '[.results[] | select(.status == "MOVED")]' /tmp/job-changes.json > /tmp/moved.jsonThe MOVED rows include the new company and (sometimes) the new title. Use these to:
- Update the contact's
current_companycolumn in the cargo Contacts model. - Surface as a "Job Changes — Last 30 Days" segment for outbound timing.
- Write a Slack notification per MOVED row.
Step 4 — (Optional) Write back to the model
If a current_company or last_job_change_at column exists on the Contacts model:
# Use cargo-ai storage / segment patterns to upsert.
# See ../../cargo-storage/SKILL.md.For pushing the MOVED set to a CRM (HubSpot custom property, Salesforce field), compose ad hoc with hubspot.upsertRecords / salesforce.upsert — discover the action via cargo-ai connection integration get hubspot and run it via orchestration action execute-batch.
Recurring monitoring (cron / play)
For continuous monitoring (e.g. weekly job-change scan), build a play: 1. Trigger: weekly cron. 2. Source: a saved segment of contacts to monitor. 3. Action node: waterfall.detectJobChange. 4. Output: write MOVED rows to a "Job Changes — Recent" segment.
For setting up a play / scheduled tool, see ../../cargo-orchestration/references/examples/plays.md.
Credit budget
waterfall.detectJobChange is 3 credits per record. Run sparingly:
| Volume | Cost |
|---|---|
| 100 contacts | 300 credits |
| 500 contacts | 1,500 credits |
| 1,000 contacts | 3,000 credits |
For weekly monitoring on a 1,000-contact segment: ~12,000 credits/month. Filter aggressively before running — only monitor segments where job changes are actionable (champions, customers, high-priority prospects).
Action shape
{"kind":"connector","integrationSlug":"waterfall","actionSlug":"detectJobChange","config":{}}. No `connectorUuid` in `config`.
Per-record inputs (any combination):
professional_email— work email.personal_email— alternative.company_domain— improves matching accuracy.company_linkedin— LinkedIn company URL.contact_linkedin— highest-coverage identifier when combined with `company_domain`.
Pass as many as you have. More identifiers = better coverage.
Output retrieval
For batch runs, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug> to retrieve results. See `../references/output-retrieval.md`.
Cargo-unique strength
No other provider in the cargo catalog has a credits-based job-change action. waterfall.detectJobChange is unique. This recipe is one of the differentiators when comparing cargo's outcome catalog to peer GTM platforms.
Recipe — LinkedIn URL lookup with strict identity validation
Use when: the user has a name and company (or email) and needs the correct LinkedIn profile URL.
Trigger phrases: "Find the LinkedIn for John Smith at Acme.", "Get LinkedIn URLs for all the contacts in this list."
Why this recipe is its own thing
LinkedIn URL resolution is the single most error-prone enrichment task. Common ways naive flows fail:
- Same first+last name, different person, wrong company → false positive.
- Person changed jobs and the resolver returns the old company.
- Resolver returns a partial / contractor profile instead of the FTE.
- Provider has stale data and returns a profile that no longer exists.
The fix is strict cross-validation: never trust the first hit. Always verify identity by enriching the candidate URL and checking the company match.
Recipe
Step 1 — Resolve a candidate URL
Use linkedin.findProfileUrl (0.25 cred) — cheapest credible source.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"linkedin","actionSlug":"findProfileUrl","config":{}}' \
--records '[
{"firstName":"John","lastName":"Smith","companyName":"Acme","companyDomain":"acme.com"},
...
]' \
--wait-until-finished > /tmp/candidates.jsonStep 2 — Validate by enriching the candidate profile
Run linkedin.enrichProfile (0.25 cred) on the candidate URL. Compare the returned company against the input company.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"linkedin","actionSlug":"enrichProfile","config":{}}' \
--records "$(jq -c '[.results[] | {linkedinUrl: .url}]' /tmp/candidates.json)" \
--wait-until-finished > /tmp/enriched.jsonStep 3 — Apply the validation gate
A candidate is valid only if all of these hold:
1. The enriched profile's currentCompany.name or currentCompany.domain matches the input company (case-insensitive, allow common variations like "Inc", "GmbH", "Ltd" stripped). 2. The enriched profile's name matches the input first+last name (case-insensitive; allow accents normalized). 3. The profile's currentRole.startDate is more recent than 1990 (sanity check that the profile is real and active).
If any check fails, reject the candidate rather than guess.
Step 4 — Fallback for rejected candidates
For rejected candidates, escalate via reverse-email lookup (only useful if you have an email):
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"reverseEmailLookup","config":{}}' \
--records '[{"email":"john.smith@acme.com"}, ...]' \
--wait-until-finished > /tmp/reverse.jsonreverseEmailLookup returns LinkedIn URL alongside a company match — apply the same validation gate from step 3.
If that also fails: mark the row as "unresolved" and surface to the user. Do not return a low-confidence URL.
Step 5 — Output the validated set
Only the rows that passed the validation gate get written back. Mark unresolved rows explicitly so the user can decide whether to research manually.
Credit budget
| Per validated contact | Cost |
|---|---|
linkedin.findProfileUrl | 0.25 |
linkedin.enrichProfile (validation) | 0.25 |
FullEnrich.reverseEmailLookup (fallback, ~30% of cases) | 2 × 0.3 = 0.6 |
| Effective: ~1.1 cred per resolved contact (with ~80% resolution rate) |
Common pitfalls
- Don't skip step 3. A first-pass
findProfileUrlhit rate is ~70%; an unvalidated rate is ~50% (false positives bring it down). Validation gate is mandatory. - Don't normalize the company name aggressively. "Acme Corp" matching "Acme Inc" is fine; "Acme Software" matching "Acme Pharmaceuticals" is not — keep the suffix awareness loose, the noun-phrase strict.
- Don't accept candidates with `currentCompany == null`. That usually means the person is between jobs; the LinkedIn profile may be stale or the resolver's match was wrong.
Action shape rules
{"kind":"connector","integrationSlug":"linkedin","actionSlug":"findProfileUrl","config":{}}. No `connectorUuid` in config. Per-record data: firstName, lastName, companyName, companyDomain (any combination accepted).
Recipe — Revive Closed-Lost deals when the original blocker is gone
Use this recipe when the user wants to systematically revisit Closed-Lost CRM deals and only re-engage the ones where the original lost-reason is no longer relevant. Tighter scope than `re-engagement.md`: input is explicitly Closed-Lost deals from the CRM (HubSpot, Salesforce, etc.), and the scan branches on lost_reason.
Trigger phrases:
- "Revisit Closed-Lost deals where the champion left."
- "Find lost deals worth reopening — anyone who lost on budget but just got funded?"
- "Replay our Closed-Lost pipeline against current signals."
- "Which lost deals are revivable this quarter?"
Why this recipe exists
Most Closed-Lost deals stay lost. But specific lost-reason categories have specific revival triggers:
lost_reason | Revival trigger |
|---|---|
champion_left / no_decision_maker | Original contact moved to a new company (waterfall.detectJobChange) — warm intro at the new account. |
price / budget / no_budget | Company raised a fresh round (cargo.fetchBusinessEvents). |
wrong_time / timing | A re-org or new exec hire (salesNavigator.searchLeads with seniority: ["VP+", "C-Level"] filter, joined date < 90d). |
feature_gap / missing_feature | Manual — replay based on your product release date vs. deal close date. |
competitor_won | Annual revisit at renewal time — check competitor satisfaction signals if available. |
The recipe runs each branch only against deals with the matching reason, keeping credit spend bounded.
Recipe
Step 1 — Pull Closed-Lost deals from the CRM
cargo-ai storage model list # find the Deals / Opportunities model UUID
DEALS_MODEL=...
cargo-ai segmentation segment fetch \
--model-uuid "$DEALS_MODEL" \
--filter '{"conjonction":"and","groups":[{"conjonction":"and","conditions":[
{"kind":"string","columnSlug":"stage","operator":"is","values":["Closed Lost"]},
{"kind":"date","columnSlug":"closed_at","operator":"olderThan","values":["90d"]}
]}]}' > /tmp/lost-deals.jsonThe 90-day floor avoids re-touching deals while they're still mentally fresh with the buyer. Adjust to match the workspace's cooling convention.
Step 2 — Branch by lost_reason
jq -c '[.records[] | select(.lost_reason == "champion_left" or .lost_reason == "no_decision_maker")]' /tmp/lost-deals.json > /tmp/lost-champion.json
jq -c '[.records[] | select(.lost_reason == "price" or .lost_reason == "budget" or .lost_reason == "no_budget")]' /tmp/lost-deals.json > /tmp/lost-budget.json
jq -c '[.records[] | select(.lost_reason == "wrong_time" or .lost_reason == "timing")]' /tmp/lost-deals.json > /tmp/lost-timing.jsonStep 3a — Champion-left branch: detect job changes
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"detectJobChange","config":{}}' \
--records "$(jq -c '[.[] | {
professional_email: .primary_contact_email,
contact_linkedin: .primary_contact_linkedin,
company_domain: .account_domain
}]' /tmp/lost-champion.json)" \
--wait-until-finished > /tmp/champion-changes.json
# Keep MOVED rows — the contact is at a new (target) company
jq -c '[.results[] | select(.status == "MOVED")]' /tmp/champion-changes.json > /tmp/revive-champion.jsonStep 3b — Budget branch: detect fresh funding
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records "$(jq -c '[.[] | {domain: .account_domain}]' /tmp/lost-budget.json)" \
--wait-until-finished > /tmp/budget-matched.json
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"fetchBusinessEvents","config":{}}' \
--records "$(jq -c '[.results[] | select(.business_id) | {business_id, event_types: ["funding"], since: "180d"}]' /tmp/budget-matched.json)" \
--wait-until-finished > /tmp/budget-events.json
# Keep deals where a funding round closed AFTER the original deal lost
jq -c '[.results[] | select((.events // []) | length > 0)]' /tmp/budget-events.json > /tmp/revive-budget.jsonStep 3c — Timing branch: detect new exec hires at the account
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchLeads","config":{}}' \
--records "$(jq -c '[.[] | {
company_domain: .account_domain,
seniority: ["VP+", "C-Level"],
function: ["Sales","Revenue Operations","Engineering"],
joined_within_days: 90
}]' /tmp/lost-timing.json)" \
--wait-until-finished > /tmp/timing-execs.json
# Keep accounts with at least one fresh exec hire
jq -c '[.results[] | select((.leads // []) | length > 0)]' /tmp/timing-execs.json > /tmp/revive-timing.jsonAdjust the function list to match where your buyer typically sits.
Step 4 — Merge into a single revive segment
jq -c -n '
([inputs[0][] | {deal_id: .deal_id, account_domain: .company_domain, revival: "champion_changed", details: .new_company}] +
[inputs[1][] | {deal_id: .deal_id, account_domain: .domain, revival: "fresh_funding", details: .events[0]}] +
[inputs[2][] | {deal_id: .deal_id, account_domain: .company_domain, revival: "new_exec", details: .leads[0]}])
' /tmp/revive-champion.json /tmp/revive-budget.json /tmp/revive-timing.json > /tmp/lost-revival.jsonStep 5 — Hand off to outreach activation
Pass /tmp/lost-revival.json to `outreach-activation.md`. The revival field becomes the signal_summary input to the personalization prompt — "They lost on budget, but just raised a $40M Series B" writes much better cold-email copy than a generic signal.
Recurring scan (cron / play)
For ongoing revival:
1. Trigger: monthly cron (lost deals don't churn signals fast enough for weekly). 2. Source: Closed-Lost deals segment with closed_at older than 90d. 3. Nodes: branch by lost_reason → run matching detector → union → write to "Lost — revival candidates" segment.
For play setup, see `../../cargo-orchestration/references/examples/plays.md`.
Credit budget
For a 300-deal Closed-Lost cohort, scanned monthly:
| Branch | Per record | Records (assumed 1/3 each) | Subtotal |
|---|---|---|---|
waterfall.detectJobChange | 3 | 100 | 300 |
cargo.matchBusiness + fetchBusinessEvents | 0.6 | 100 | 60 |
salesNavigator.searchLeads | 2 | 100 | 200 |
| Total monthly | — | 300 | 560 |
Much cheaper than the broader `re-engagement.md` scan because each branch only runs on the relevant subset.
Action shape
Every action follows: {"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config` — see `../../cargo-orchestration/references/examples/actions.md`.
Output retrieval
For batch runs, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug>. See `../references/output-retrieval.md`.
Related
- `re-engagement.md` — broader: any stale contact, not specifically Closed-Lost deals.
- `icp-discovery.md` — upstream: surfaces why deals are being lost (Closed-Won vs Closed-Lost diff), which informs the lost-reason categorization here.
- `outreach-activation.md` — downstream: turns the revival segment into send-ready outreach.
Recipe — Activate a signal segment as personalized outreach
Use this recipe when the user has a signal-driven segment ready (recent fundraise, job change, tech intent, ICP-fit accounts, etc.) and wants to turn it into send-ready outreach — enriched contacts, LLM-personalized variables, handed off to their sequencer or CRM. Bridges the `../guides/writing-outreach.md` guide to actual execution.
Trigger phrases:
- "Take this segment and write outreach for it."
- "Personalize a first-touch email for every contact in the recently-funded segment."
- "Build a sequence-ready list from job changes this week."
- "Generate first lines for the tech-intent companies."
Why this recipe exists
Signal recipes (funding-watch, job-change-monitoring, tech-intent, portfolio-prospecting) all produce a segment. They stop at "here's a list with a signal." The next step — enrich, personalize, hand off — has the same shape regardless of the signal. This recipe captures that shape once.
The handoff target is the workspace's sequencer of choice (Outreach, Salesloft, Apollo, HubSpot Sequences, Salesforce Cadences). The recipe stops at "send-ready variables" and points at cargo-ai connection integration get <slug> for the final push.
Recipe
Step 1 — Pull the signal segment
cargo-ai storage model list # find Companies / Contacts model UUID
MODEL_UUID=...
cargo-ai segmentation segment list # find the signal segment, e.g. "Recently Funded — last 30d"
cargo-ai segmentation segment fetch \
--model-uuid "$MODEL_UUID" \
--filter '{"conjonction":"and","groups":[{"conjonction":"and","conditions":[
{"kind":"string","columnSlug":"signal","operator":"is","values":["funding","job_change"]}
]}]}' > /tmp/signal-segment.jsonStep 2 — Resolve the right contacts
If the segment is company-level (e.g. recently funded), pull target personas at each account:
# Use salesNavigator for precision, peopleDataLabs for scale.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchLeads","config":{}}' \
--records "$(jq -c '[.records[] | {
company_domain: .domain,
title_keywords: ["VP", "Director", "Head"],
function: ["Sales", "Revenue Operations"]
}]' /tmp/signal-segment.json)" \
--wait-until-finished > /tmp/contacts.jsonIf the segment is already contact-level (e.g. job-change MOVED rows), skip this step — use those rows directly.
Step 3 — Enrich each contact (email + LinkedIn + firmographics)
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"enrichProspectDetails","config":{}}' \
--records "$(jq -c '[.results[] | {
first_name, last_name,
company_domain: .company_domain,
contact_linkedin: .linkedin_url
}]' /tmp/contacts.json)" \
--wait-until-finished > /tmp/enriched.jsonWaterfall returns the best-coverage email, phone, and a normalized contact profile. For premium contact data (mobile direct dials, top-tier accuracy), swap in FullEnrich.enrichPerson.
Step 4 — Verify emails before personalizing
Cheap insurance against bounces and sender-reputation damage:
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"verifyEmail","config":{}}' \
--records "$(jq -c '[.results[] | {email}]' /tmp/enriched.json)" \
--wait-until-finished > /tmp/verified.json
# Keep only deliverable
jq -c '[.results[] | select(.status == "valid")]' /tmp/verified.json > /tmp/deliverable.jsonStep 5 — Generate a personalized first line per contact
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"anthropic","actionSlug":"instruct","config":{"model":"claude-haiku-4-5","temperature":0.3}}' \
--records "$(jq -c '[.results[] | {
prompt: ("You are writing the opening line of a cold email. The recipient is " + .first_name + " " + .last_name + ", " + .title + " at " + .company_name + ". Signal triggering this outreach: " + .signal_summary + ". Write ONE sentence that references the signal naturally and ties it to a relevant business outcome. No greeting. No follow-up. ≤30 words.")
}]' /tmp/deliverable.json)" \
--wait-until-finished > /tmp/personalized.jsonFor higher quality at higher cost, swap claude-haiku-4-5 for claude-sonnet-4-6. For 30× cheaper at scale: openAi with gpt-4o-mini (0.006 credits/call vs Haiku's 0.2).
Step 6 — Hand off to the sequencer
Compose the send-ready payload — one row per contact with email, signal, and the personalized first line:
jq -c '[
(input_filename as $f | inputs)
| .results[] as $p
| {email: $p.email, first_line: $p.text, signal: $p.signal_summary}
]' /tmp/deliverable.json /tmp/personalized.json > /tmp/send-ready.jsonThen push to the user's sequencer. Discover the action via:
cargo-ai connection integration get outreach # Outreach.io
cargo-ai connection integration get salesloft # Salesloft
cargo-ai connection integration get hubspot # HubSpot Sequences
cargo-ai connection integration get salesforce # Salesforce CadencesThen execute the discovered action with orchestration action execute-batch, passing the per-contact payload. Do not invent `actionSlug` values — list them from the integration first.
Recurring activation (cron / play)
For ongoing signal-driven outreach:
1. Trigger: weekly cron on the signal segment. 2. Workflow nodes: signal-segment → enrich → verify → personalize → sequencer push. 3. Source: the saved signal segment (e.g. "Recently Funded — last 30d"). 4. Output: send-ready payload + sequence-add action.
For play setup, see `../../cargo-orchestration/references/examples/plays.md`.
Credit budget
For a 500-contact signal segment (waterfall + verify + Haiku personalization):
| Step | Per record | 500 contacts |
|---|---|---|
waterfall.enrichProspectDetails | 1 | 500 |
waterfall.verifyEmail | 0.5 | 250 |
anthropic.instruct (Haiku) | 0.2 | 100 |
| Total | 1.7 | 850 |
Halve to ~425 credits by switching personalization to openAi.instruct with gpt-4o-mini.
Action shape
Every action follows: {"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config` — see `../../cargo-orchestration/references/examples/actions.md`. Cross-node interpolation in node graphs: {{nodes.<slug>.<field>}}.
Output retrieval
For batch runs, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug>. See `../references/output-retrieval.md`.
Related
- `writing-outreach.md` — provider routing, prompt patterns, model selection.
- Upstream signal recipes that produce input segments for this recipe: `funding-watch.md`, `job-change-monitoring.md`, `tech-intent.md`, `portfolio-prospecting.md`.
Recipe — Investor portfolio → contacts → outbound
Use this recipe when the user wants to prospect into the portfolio of a specific investor or accelerator. Common pattern: a partner / accelerator program is a known proxy for ICP fit, so all their portfolio companies are pre-qualified.
Trigger phrases:
- "Find every company backed by Sequoia and reach out to their CTOs."
- "Prospect into the YC W26 batch."
- "Build a list of CFO contacts at all Insight Partners portfolio companies."
- "Show me every founder funded by First Round Capital."
Why this is its own skill
Portfolio prospecting has a specific shape that doesn't fit the generic prospecting pipeline:
- The sourcing filter (investor name, fund, batch) isn't expressible in salesNavigator's UI-style filters.
peopleDataLabs.queryCompaniesis the right tool — its SQL API can filter on investor / funding fields that no other priority-stack provider exposes.- Once portfolio companies are sourced, you typically want a tight per-company contact cap (1–3 prospects per portfolio company) — different from generic at-scale lead search.
Recipe
Step 1 — Source portfolio companies via investor filter (PDL SQL)
queryCompanies accepts a SQL string — array-membership filters like investor name require SQL (cargo's {conjonction, groups, conditions} filter shape can't express summary.investors LIKE %X%).
cargo-ai orchestration action execute \
--action '{"kind":"connector","integrationSlug":"peopleDataLabs","actionSlug":"queryCompanies","config":{}}' \
--data '{
"query": "SELECT * FROM company WHERE summary.investors LIKE %Sequoia Capital%",
"limit": 200
}' \
--wait-until-finished > /tmp/portfolio.jsonFor accelerator batches (e.g. YC W26), the investor field still works — accelerators are stored in summary.investors alongside VCs. Common SQL fields useful here:
| PDL SQL field | Use for |
|---|---|
summary.investors | Investor / accelerator filter (use LIKE %Name%) |
latest_funding_stage | Stage filter (e.g. 'series_b', 'seed') |
total_funding_raised | Total funding raised (range query) |
industry | Industry filter |
employee_count | Headcount range |
location.country / location.locality | Geography |
tags | Topic tags |
See PDL's SQL reference for the full schema.
Step 2 — Match portfolio companies against cargo
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"matchBusiness","config":{}}' \
--records "$(jq -c '[.results[] | {domain: .website}]' /tmp/portfolio.json)" \
--wait-until-finished > /tmp/matched.jsonStep 3 — Enrich firmographics on the portfolio
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"cargo","actionSlug":"enrichBusinessFirmographics","config":{}}' \
--records "$(jq -c '[.results[] | select(.business_id) | {business_id}]' /tmp/matched.json)" \
--wait-until-finished > /tmp/firmo.jsonStep 4 — Find contacts at each portfolio company
Cap tightly — for portfolio prospecting, 1–3 contacts per company is usually right. Targets are typically founder / CEO / role-of-interest.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"salesNavigator","actionSlug":"searchLeads","config":{}}' \
--records "$(jq -c '[.results[] | {
keywords: "Founder OR CEO OR CTO",
company: {linkedinIds: [.linkedinId]},
limit: 3
}]' /tmp/portfolio.json)" \
--wait-until-finished > /tmp/contacts.jsonStep 5 — Find emails
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"FullEnrich","actionSlug":"findEmail","config":{}}' \
--records "$(jq -c '[.contacts[] | {firstName, lastName, domainName: .companyDomain, linkedinUrl: .linkedinUrl}]' /tmp/contacts.json)" \
--wait-until-finished > /tmp/emails.jsonStep 6 — Verify emails
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"waterfall","actionSlug":"verifyEmail","config":{}}' \
--records "$(jq -c '[.results[] | select(.email) | {email}]' /tmp/emails.json)" \
--wait-until-finished > /tmp/verified.jsonStep 7 — Personalize outbound (optional)
Use cargo.enrichProspectDetails to pull recent LinkedIn posts, then anthropic.instruct for a personalized opener referencing the investor + recent portfolio activity. See `../guides/writing-outreach.md` for prompt patterns.
Credit budget
For 200 portfolio companies × 3 contacts each = 600 prospects:
| Step | Cost per record | Records | Subtotal |
|---|---|---|---|
| 1. queryCompanies (single call returning 200) | — | 1 call | 3 |
| 2. matchBusiness | 0.5 | 200 | 100 |
| 3. enrichBusinessFirmographics | 0.5 | 200 | 100 |
| 4. searchLeads (3 contacts each) | 0.02 | 600 | 12 |
| 5. FullEnrich.findEmail | 1 | 600 | 600 |
| 6. waterfall.verifyEmail | 0.1 | 600 | 60 |
| Total | ~875 credits for 600 verified contacts at 200 portfolio companies |
Discovery sequence
# Confirm priority connectors
for slug in peopleDataLabs salesNavigator FullEnrich waterfall cargo; do
cargo-ai connection connector list --integration-slug "$slug" \
| jq -e '.connectors | length > 0' > /dev/null \
&& echo "✓ $slug" || echo "✗ $slug"
doneAction shape
{"kind":"connector","integrationSlug":"<slug>","actionSlug":"<slug>","config":{}}. No `connectorUuid` in `config`.
Output retrieval
After each batch step, use cargo-ai orchestration run download-outputs --workflow-uuid <uuid> --output-node-slug <slug> for output data.
When the investor isn't in peopleDataLabs
If peopleDataLabs.queryCompanies doesn't recognize the investor name (e.g. very small fund, regional accelerator), fall back to:
apolloioif its investor coverage is stronger for the niche.firecrawl.scrapeon the investor's portfolio page (if public) → LLM extract viaanthropic.instruct.- File a
cargo-ai workspaceManagement report createif neither works — surfaces the gap to the cargo team.
When stuck — file a workspace report
See `../../cargo-workspace-management/SKILL.md` (Reports section).
Related skills
How it compares
Use cargo-gtm as the GTM router; invoke cargo-orchestration for workflow graph design and cargo-analytics for run metrics outside prospecting recipes.
FAQ
What must run before cargo-gtm executes provider actions?
cargo-gtm requires reading the matching phase guide or recipe before any cargo-ai orchestration action execute call. The skill blocks execution until the correct finding-companies, enriching-and-researching, or writing-outreach doc is opened to avoid wasted credits.
Which providers does cargo-gtm prioritize?
cargo-gtm leads with six providers: salesNavigator for sourcing, cargo native enrichment, waterfall for multi-source enrichment and verifyEmail, FullEnrich for premium contact lookup, theirStack for tech intent, and peopleDataLabs as backfill at a flat 3-credit tier.
How does cargo-gtm fetch workflow output data?
cargo-gtm directs agents to cargo-ai orchestration run download-outputs with workflow-uuid and output-node-slug, returning a signed URL to CSV or JSON output instead of heavier full run downloads.