How to Enrich 20,000 Leads from Snowflake with AI (Without a $10,000 Bill)
A founder in one of our group chats recently enriched 20,000 contacts using a coding agent. It ran for 24 hours and used 12 billion tokens. On frontier-model pricing, that is roughly $10,000 of compute for a job that is, at its core, "look up some facts about each of these people and companies."
The result was fine. The bill was not. And the fix is not a cheaper model or a bigger discount. It is running the same work in a different shape. This is the walkthrough for doing bulk AI enrichment the right way: from a SQL query, one independent agent run per row, in parallel.
Why enrichment blows up
Enrichment is row-shaped work. Each contact needs the same process: find the company, pull traffic and headcount signals, check recent news, score the fit. Row 4,000 does not depend on row 3,999 in any way.
Run that as one long agent session anyway, and you pay for the independence you ignored. The agent carries the full history of every previous row into each new one, so token cost grows quadratically, and accuracy drops as the context fills with stale, irrelevant rows. When we benchmarked 200 multi-turn agent tasks, the serial run cost 16x more than parallel jobs and lost ten points of accuracy doing it. At 10,000 tasks the gap grows to roughly 8,700x. The full data is in Serial vs. Parallel AI Agents.
12 billion tokens for 20,000 contacts is 600,000 tokens per contact. The actual research for one contact needs a few thousand. The rest is the shape.
The right architecture
Three pieces:
- Your warehouse selects who to enrich. The list of leads, and the rules for which ones are worth spending money on, already live in Snowflake. Keep them there.
- A predefined agent does one row's work. The agent is configured once, in Cotera, with its tools (web search, SimilarWeb, LinkedIn insights), its instructions, and its permissions. It knows how to enrich a lead. It never sees the other 19,999.
- The query fans out and the results come back as a column. Every row is an independent, parallel agent run. Cotera handles queueing, retries, and rate limits; Snowflake stores the output like any other data.
The walkthrough
Install the Cotera Native App from the Snowflake Marketplace (the install guide covers it, about ten minutes), and pick an enrichment agent. You can build your own or copy one from Solutions, like the Lead Enricher & Qualifier or the Company Growth Analyzer.
Step 1: select who is worth enriching. This is the step the $10,000 run skipped. Not every contact deserves an agent run. Let SQL do what SQL is for:
CREATE OR REPLACE TABLE ops.leads_to_enrich AS
SELECT
lead_id,
company_domain
FROM analytics.inbound_leads
WHERE enriched_at IS NULL
AND email_status = 'valid'
AND company_domain IS NOT NULL
QUALIFY ROW_NUMBER() OVER (PARTITION BY company_domain ORDER BY created_at) = 1;
Deduplicating by domain alone often cuts a contact list by a third: you need the company researched once, not once per contact at the company.
Step 2: call the agent per row.
CREATE OR REPLACE TABLE ops.lead_enrichment_results AS
SELECT
lead_id,
company_domain,
cotera.agents.agent('<your agent id>', company_domain) AS enrichment
FROM ops.leads_to_enrich;
Each row fires one agent run with a fresh context containing exactly one domain. The agent does its three or four tool calls, and the structured result lands in the enrichment column. Retries on flaky APIs, backoff on rate limits, and run-level observability are Cotera's job, not a script you babysit.
Step 3: use the results like warehouse data, because they are. Join the enrichment back to your CRM sync, filter to the leads that scored above threshold, or hand the winners to a second agent that drafts outreach. Batch enrichment stops being a special project and becomes a query in your existing pipeline, schedulable like any other.
SELECT lead_id, company_domain, enrichment
FROM ops.lead_enrichment_results
WHERE enrichment:fit_score::int >= 8
ORDER BY enrichment:fit_score::int DESC;
What it costs instead
The honest answer is that cost depends on the agent: an enrichment that makes four external tool calls costs more per row than a pure text classification. The number that matters is the ratio between shapes, and that ratio is not close. In our benchmark, the same workload ran at $12 serial versus $0.73 parallel at 200 tasks, and the parallel cost stays flat per task as volume grows while serial compounds.
The other saving is the one nobody prices in: the 24 hours becomes minutes, because 20,000 independent jobs run concurrently instead of one at a time in a queue disguised as a conversation. And the accuracy is higher, because no row is reasoning over 19,999 rows of somebody else's context.
Common mistakes
- Enriching everyone. Selection is the highest-leverage line of SQL in this whole pipeline. Score first, enrich the subset.
- Putting the enrichment logic in the prompt column. Keep the how in the agent definition, versioned and reviewed once. SQL should only decide the who.
- One mega-agent that enriches, scores, and writes outreach. Split independent steps into separate agents. Decomposed small-model agents matched a frontier model in our refund-pipeline test, at a fraction of the cost.
- Ignoring writeback. Enrichment that lives in a results table nobody joins is spend, not signal. Land it back on the lead record where a rep will see it.
FAQ
Does my whole lead list get sent to the agent? No. Each agent run receives only what your query passes it, typically one domain or one lead record per row, subject to your existing Snowflake permissions.
How long does 20,000 rows take? Rows run as parallel jobs, so wall clock is set by concurrency and per-integration rate limits rather than by row count. Minutes to low hours for tool-heavy agents, not days.
Can I schedule this? Yes. Wrap the query in a Snowflake task for nightly enrichment of new leads, or run the same agent definitions on a schedule from the Cotera side at warehouse scale. See Cotera for Enterprise.
What if a row fails halfway? Runs are stateful on Cotera's side: failures retry with backoff, and completed rows do not re-run. That is most of the reason to use an agent runtime instead of a script.
Try These Agents
Browse the full catalog on Solutions, or start with these:
- Lead Enricher & Qualifier — Qualify inbound leads from CRM and warehouse data with structured scoring
- HubSpot Contact Enrichment — Enrich contacts from external sources and write structured updates back to CRM
- Company Growth Analyzer — Research company growth, traffic trends, and competitive landscape from a domain