Apollo is the default dataset for B2B cold outbound. It is also the place most operators waste their first month of credits without ever putting a verified email in front of a verified buyer.
The pattern goes like this. You sign up on the Basic tier, get your 1,000 monthly email credits, click into People Search, type "Operations Director" into the title box, narrow by country, and start enriching. By the end of week one the credit pool is down to single digits and you have a CSV of around 600 contacts. Half of those contacts left their job last year. A material chunk are not in the niche you actually sell to. The bounces will come in a fortnight, when Instantly starts flagging the sending domains because Apollo's real-world deliverability sits closer to 65-75% than the 91% it markets. Your sender reputation takes the hit, not Apollo's.
The credit pool isn't the constraint. The wrong order of operations is. Apollo bills per match attempt on the bulk-enrich endpoint. Every empty match still costs a credit. You can run a batch of ten people at the highest seniority tier and walk away with three emails and seven debits, and Apollo's own dashboard counter will undercount it by around 25% if you don't track attempts directly.
This is the playbook for pulling verified leads out of Apollo without burning the bank, written for anyone who runs cold outbound at production volume. The flow has been hardened across round about forty client runs on the cold outbound engagements we operate for clients since early 2026, and saves us thousands of credits per campaign. If you're running an in-house programme, the same flow ports cleanly across.
Apollo's three endpoints and their economics
Apollo exposes three endpoints that matter for lead-gen. They have radically different economics, and the entire credit-efficient flow depends on knowing which one drains the pool.
Company Search hits /mixed_companies/search. Free. I confirmed this empirically in early 2026 by reading the rate-limit headers after a 100-record call: 5,999 of 6,000 hourly requests remained, no credit decrement at all. Returns organisation-level records with primary domain on around 95% of rows, plus SIC and NAICS codes, founded year, revenue, headcount growth metrics, the lot. Enough to drive verification. Not enough to skip it.
People Search hits /mixed_people/api_search. Also free. Returns preview-only records, which means the response is stripped: no email, no full last name, no seniority tag, no city, no LinkedIn URL, no nested organisation domain. What you do get is the person's id, their title, an obfuscated last name, and a has_email boolean that tells you whether Apollo has a verified email on file. That last field is the single most useful field in the entire response.
Bulk Enrich hits /people/bulk_match. This is the one that costs. One credit per business-email hit, ten records per call, no waterfall and no personal-email reveal unless you opt in. Returns the rich record: full name, verified email, seniority, city, country, full nested organisation. This is the only call that drains the credit pool. Everything else is free.
Now the gotcha. Apollo bills per match attempt, not per row that came back with an email. Submit a batch of ten people, Apollo only fulfils seven, you spent ten credits and got seven usable contacts. Our internal credit counter undercounted by roughly 25% before April 2026 because we were incrementing on rows-with-email instead of len(matches). If you don't count attempts directly, your --max-credits budget is meaningless against Apollo's actual billing.
The credit-efficient flow falls out of the economics. Verify companies for free first. Drill down for free. Only spend the bank on the final enrichment of decision-makers you've personally confirmed are worth a credit each.
The "50% of Apollo exports are bogus" problem
Apollo's keyword-tag filter is a useful coarse filter and a terrible fine filter. Tags are AI-curated from company website copy, which means they pick up adjacency. A company that helps recruitment agencies grow gets tagged with most of the same terms a recruitment agency itself would have. Same for software vendors selling into accountancy practices, training providers running courses for accountants, asset-finance shops lending to accountants. They all collide in the same tag bucket.
I ran a tight UK accountancy filter in late April. Fifteen include tags around audit and corporate finance, six exclude tags for the obvious adjacents, employee range 11 to 200. Apollo returned 1,946 companies. Looked clean from the count probe. SIC code distribution was overwhelmingly accountancy.
Then I started reading the homepages.
About 30% were genuine accountancy practices doing audit, tax, and advisory for SMEs. Around 40% were software vendors selling practice-management or tax-prep tools to accountants. Roughly 20% were recruiters that specialise in placing finance staff at accountancy firms. The remainder split between training providers, asset-finance shops, and the odd actual law firm Apollo had mistagged. Run bulk enrich on the full 1,946 without filtering further and you'd spend somewhere north of 4,500 credits to learn which 600 you actually wanted, and the campaign would reply at 0.4% because three quarters of the inboxes were the wrong industry.
Tag-level filtering catches the obvious mismatches and not much else. Apollo's exclude-tag parameter, q_not_organization_keyword_tags, is undocumented but production-stable, and we use it to chop "rpo" and "executive search" and "contract staffing" out of recruitment runs every time. That layer pulls maybe 15-25% of the pool out before verification. The remaining 50%-ish bogus rate has to be solved with something Apollo doesn't offer, because Apollo's data model has no way of knowing what the company's homepage actually says about itself.
That something is the AI verification layer.
The AI verification layer
The verification layer reads each candidate company's homepage and decides, binary, whether it's actually in the niche. There is no tag, no SIC code, no firmographic shortcut. We read the website. Or rather, FireCrawl reads the website and Claude Sonnet reads what FireCrawl returned.
FireCrawl Cloud handles the scrape side. The proxy: auto setting bypasses Cloudflare on roughly 95% of domains we throw at it, including the bot-protection setups that block the underlying browser. Basic mode costs one credit per scrape. Bot-protected sites trigger an enhanced retry at five extra credits. Average across a typical B2B niche is around 1.75 credits per company. Cheaper than I expected when I first costed it, and far cheaper than the alternative, which is paying enrichment credits on garbage rows and learning the niche match by reading the bounces.
The scrape produces one markdown payload per company, capped at 6,000 characters. Each one gets a status field: ok, homepage_failed, blocked, dead_domain, tls_error, no_domain, or domain_for_sale. Anything that isn't ok is auto-classified at split time without an LLM call, because there's no markdown for the LLM to read. Companies with status ok go into batches of 30 and get sent to Sonnet subagents in parallel.
We use Sonnet, not Haiku. The April 30th UK accountancy run was the calibration point. On Haiku, around 35% of with-markdown entries came back as unverified because the model couldn't be bothered to commit on sparse-but-readable copy. Sonnet handled the same 61 entries by correctly classifying 50 as no (recruiters, software vendors, training providers, the asset-finance shops the markdown made obvious within two paragraphs), 4 as yes, and leaving 7 genuine bot-protection cases. The Max plan covers Sonnet at no extra cost, so the only argument for Haiku was speed. The speed gain disappears once you reckon on the manual review tax of a 35% unverified rate.
Each verdict comes back as {id, verdict, reason}. The reason field is the anti-hallucination control. Every yes verdict has to quote a specific phrase from the markdown that justifies it. If the reason is generic ("appears to match the niche"), we treat it as a hallucination and downgrade the verdict to unverified. If the reason quotes a clear vertical marker from the homepage ("forensic accounting and dispute resolution for SME owners"), we keep it.
The full scrape-and-classify pass runs in around two minutes for 1,500 companies, at the FireCrawl cloud's 50-concurrent-browser cap. Total credit spend before any Apollo enrichment happens: round about 2,600 FireCrawl credits, give or take. That feels like a lot until you compare it with running bulk_match on 1,946 unverified companies and ending up with a list that's half wrong.
The four-layer credit defence
By the time we reach the paid step, the candidate pool is the verified-yes companies only. That alone removes roughly half the credit waste. The other half is Apollo behaviour quirks that keep biting if you don't put explicit guards in place.
Layer 1 is the upstream filter on the People Search drilldown. Set contact_email_status: ["verified"] on the request body. Apollo only returns people for whom it has a verified email on file, which means the bulk_match endpoint never sees people it can't fulfil. Empirical impact across the 2026 runs: drops 25-40% of the raw pool before paying anything. This was the missing piece that cost us roughly 800 credits on the April 22nd UK recruitment run before we added it. I added it the same evening and have never run a campaign without it since.
Layer 2 is a belt-and-braces has_email=True check inside the selection script. Shouldn't fire if Layer 1 is doing its job, and in production it almost never does. Costs nothing. Catches the failure mode where someone overrides the drilldown filter for a one-off run and forgets to put it back.
Layer 3 is a pre-flight batch cost check inside enrich.py. Before each bulk_match batch goes out, the script verifies that current spend plus batch size would not breach --max-credits. Without this guard you can overshoot the user's budget ceiling by up to 9 credits per batch (the worst case for a 10-record call where the budget had 1 credit remaining). Trivial to write. Prevents quiet overruns when the budget is tight.
Layer 4 is response-side enforcement. After bulk_match returns, only rows where email_status == "verified" get written to the CSV. Anything else (likely, unverified, invalid, null, missing entirely) is counted as dropped_not_verified and discarded. The email_status value also goes into the CSV as its own column, so the user can spot-check. This is the final guarantee that the leads CSV contains only verified emails even if Apollo's behaviour drifts mid-run, a plan tier silently changes, or someone has overridden the drilldown filter for a campaign requiring volume.
Two more controls earn their place alongside the four layers. The first is idempotent=False on every bulk_match request. Apollo can debit credits server-side before returning a 5xx or dropping the connection, so blind retry would double-charge. The user sees an explicit error and decides whether to resubmit. The second is the post-run plan-credit handler. Apollo's plan-level credit pool is separate from the user-facing --max-credits budget, and the script can't predict when Apollo will return 422 mid-run. It can only handle it cleanly when it does.
Stack the four layers and credit waste collapses to single digits per campaign. Strip any one of them and the campaign quietly bleeds. The April 22nd run that cost us 800 credits before Layer 1 went in is still the loudest example I can point at when someone asks why we're so insistent on the upstream filter.
The title-filter blind spot
Apollo's include_similar_titles=true flag is essential and asymmetric. It expands "Sales Director" to "Director of Sales" and similar rephrasings. It does not expand "Sales Director" to bare "Director", "Operations Manager" to "Director of Operations", or "Marketing Director" to "General Manager". The expansion is conservative and one-directional. If you only put compound titles into your person_titles list, you miss everyone whose title is the bare-noun form.
This matters for UK SMEs under fifty employees more than anything else. The buyer for an outsourced cold-email service at a 25-person training company is often titled simply "Director", or "Operations Director", "Operations Manager", "General Manager", or "Centre Manager". The founder of a 12-person agency is frequently listed only as "Director" on LinkedIn even when she owns the firm outright.
The April 24th UK Corporate Training and L&D run is the calibration point we keep referring back to. The original title list was the textbook commercial-leadership set: Commercial Director, Sales Director, Head of Sales, BD Director, the lot. It found verified contacts at 23% of the verified-yes companies, 184 of 800. We added the bare-noun set, Director / Operations Director / Operations Manager / General Manager / Centre Manager / Training Manager, re-ran the drilldown, and coverage lifted to 60%. 479 of 800 companies. Zero extra Apollo credits spent because the People Search drilldown is free. The remaining 321 split between companies with no Apollo verified-email people at all (134) and companies with verified people only in correctly-excluded delivery titles (197).
The bare-noun set lives in our global include list now, not in a per-niche adapter. We add it on every run, on every niche, even when the niche looks obviously commercial-leadership-driven. The cost of leaving it out sits somewhere between a quarter and a half of the addressable pool.
The dry-run cost gate
The dry-run is the last guard before the credit pool gets touched.
enrich.py --dry-run reads the verified-yes companies, joins them to the drilldown people list, applies the title-tier ranking, takes up to N leads per company, and computes the selection without making a single paid call. It returns four numbers: selected_count, unique_companies, credits_required, and skipped_preview_no_email. No bulk_match, no bank movement.
Then the script prints a budget panel:
Selected: 4,086 people across 1,362 verified-yes companies
Leads per company: 3
Credits required: 4,086
Your max-credits: 2,500
Status: OVER BUDGET. First 2,500 (highest priority by title tier) will be enriched.
Over-budget lights up red and waits for confirmation. Under-budget still requires an explicit "proceed" before the script touches the credit pool. There is no autopilot mode. Every paid run gets approved at the budget panel.
I don't always know in advance whether a niche will land over or under budget, particularly on a first run for a new vertical. The dry-run takes around four seconds to compute, and lets us narrow further (drop leads-per-company from three to two, tighten the title list, exclude a sub-vertical) before pressing go. On the April 22nd recruitment run, we caught a 5,400-credit selection against a 2,500 ceiling at the dry-run step, narrowed to 2 leads per company, came in at 2,724, and dropped one sub-vertical to land at 2,400. Total time from dry-run to approved budget: roughly six minutes, and the credit pool never moved.
What you've got at the end
What you've built by the end of this flow is a CSV with a specific shape. Every row has a verified email. Every row's company has been read by a Sonnet subagent and confirmed in-niche, with the markdown phrase that justified the verdict still sitting in verdict_reason. Every row's job title is on the include list and not on the exclude list. Every row's person location matches the country argument. The credit spend is logged batch by batch and capped at the budget you approved at the dry-run step.
Apollo's data quality stops being the variable. The variable becomes whatever you do with the list next, which is the easier problem.
That doesn't mean Apollo gets you everyone. Roughly 30 to 60% of verified-yes companies come back from Apollo with no contacts at all, depending on niche maturity and Apollo's data coverage. The flow writes those into a separate _no_contacts.csv so they're clearly labelled, and a second-leg enrichment tool (we use Any Mail Finder for the no-contacts bucket) runs a different match model on the same domain list. Different cost, different coverage, complementary stage of the same flow.
If you'd rather not run any of this yourself, this is the work we operate end to end on Pipeline. We custom-build the lead and intent-signal engine per company, run the verification layer on every campaign, and you turn up to the booked meetings.


