~ / guides / How to Scrape ZoomInfo & Apollo (Python Guide)

How to Scrape ZoomInfo & Apollo (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • I hit both targets live in June 2026 with plain Python. ZoomInfo company pages returned 403 with a Cloudflare challenge (cf-mitigated: challenge, the 'Just a moment...' page). Apollo company pages returned 410 Gone. Neither parses with requests.
  • ZoomInfo's data lives behind a Cloudflare JS challenge and a login wall. Apollo deliberately retired its public company pages (410) and serves the real data only inside the logged-in app.
  • Both sell B2B contact data, which is personal data. GDPR and CCPA apply the moment you store an EU or California resident's name, email, or phone, regardless of any anti-bot bypass.
  • Because the DIY path is blocked at the door on both sites, a scraper API like ChocoData that renders JS and rotates residential IPs is the realistic technical route, with the legal limits unchanged.

ZoomInfo and Apollo are the two biggest B2B contact databases on the web: company firmographics, employee headcounts, direct dials, and work emails. That makes them obvious scraping targets and two of the hardest. I spent June 2026 pointing a plain Python scraper at both. The short version: ZoomInfo company pages return a 403 Cloudflare challenge, and Apollo company pages return 410 Gone. Neither hands you parseable HTML from a logged-out request. This guide shows exactly what I got, why both block, and the scraper-API route that is the realistic option for public data on either site.

Can you legally scrape ZoomInfo and Apollo?

Scraping public data from these sites sits in three separate legal buckets you weigh independently: US computer-crime law, each vendor’s terms of use, and data-protection law. The data-protection layer matters most here because both products are built on personal data.

On US computer-crime law, the anchor case is hiQ Labs v. LinkedIn. On April 18, 2022, the Ninth Circuit ruled that scraping publicly available data likely does not violate the Computer Fraud and Abuse Act, because the CFAA’s “without authorization” bar does not reach data open to the public without a login. That precedent helps far less here than on LinkedIn, because the substantive ZoomInfo and Apollo records sit behind a Cloudflare challenge or a login wall, so they are not “open to the public without a login” in the first place.

On contract, both vendors restrict automated access in their terms of use, and breaching those terms is a civil matter (account termination, breach claims) separate from the criminal CFAA question. I did not retrieve a verbatim anti-scraping clause from either live terms page during testing, so I am not quoting one here, confirm the current wording on each vendor’s own terms page before you rely on it.

On data protection, the picture is the sharpest. Both companies sell B2B contact data: names, job titles, work emails, direct phone numbers. That is personal data under GDPR and the CCPA even when it is work contact information. ZoomInfo’s own privacy policy states its “search technology scans the web and gathers publicly available information” and gives individuals a route to opt out of its database, which is exactly the data-subject-rights machinery GDPR requires. Scraping these databases makes you a separate data controller with your own obligations: a lawful basis, notice, and honoring deletion requests. I cover the general framing in my is web scraping legal guide. For these two targets specifically, treat every record as personal data and get counsel before processing identifiable EU, UK, or California residents at scale.

What data is available on ZoomInfo and Apollo?

Both expose rich firmographic and contact data inside their products; only a thin slice is reachable from a logged-out page, and even that is gated by anti-bot defenses. Here is what each surface holds, based on the public page structure and each vendor’s own product documentation:

Data fieldZoomInfoApolloLogged-out access
Company name, website, HQ addressYesYesBlocked (challenge / 410)
Industry, founding year, ownershipYesYesBlocked
Revenue and employee-count rangesYesYesBlocked
Tech stack / technologies usedYesYesBlocked
Employee names and job titlesYesYesLogin required
Work email addressesYesYesLogin + credits
Direct dial / mobile numbersYesYesLogin + credits
Org chart / reporting linesYesPartialLogin required
Funding rounds, competitorsYesYesBlocked

The pattern on both sites is the same: firmographic data exists on a public URL but is shielded by the anti-bot layer, and the valuable contact data (emails, direct dials) only renders for a logged-in account and usually costs credits to reveal. There is no logged-out surface that hands you contact records in clean HTML the way LinkedIn’s guest jobs endpoint does. The next section shows exactly how each site blocked my requests.

How do ZoomInfo and Apollo block scrapers?

ZoomInfo uses a Cloudflare JavaScript challenge; Apollo returns 410 Gone on its public company pages and keeps the real data inside the logged-in app. I confirmed both live in June 2026 with Python’s requests and a real browser User-Agent. Here is the probe I ran:

import requests

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
     "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
headers = {
    "User-Agent": UA,
    "Accept-Language": "en-US,en;q=0.9",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}

# ZoomInfo company page
z = requests.get(
    "https://www.zoominfo.com/c/zoominfo-technologies-llc/364098188",
    headers=headers, timeout=20,
)
print("ZoomInfo:", z.status_code, "| server:", z.headers.get("Server"),
      "| cf-mitigated:", z.headers.get("cf-mitigated"),
      "| 'Just a moment' in body:", "Just a moment" in z.text)

# Apollo company page
a = requests.get(
    "https://www.apollo.io/companies/apollo-io/5e66b6381e05b4008c8331b8",
    headers=headers, timeout=20,
)
print("Apollo:", a.status_code, "| server:", a.headers.get("Server"),
      "| body:", repr(a.text[:40]))

This is the real output:

ZoomInfo: 403 | server: cloudflare | cf-mitigated: challenge | 'Just a moment' in body: True
Apollo: 410 | server: cloudflare | body: 'fault filter abort'

Two different walls. ZoomInfo returns 403 with cf-mitigated: challenge and the “Just a moment…” interstitial, Cloudflare’s bot-management JS challenge. A plain HTTP client cannot execute the challenge script, so you never reach the real page; the 5,948-byte body I got back was the challenge page, not company data. Solving it needs a real browser engine plus a clean residential IP, and ZoomInfo also gates the substantive data behind a login.

Apollo returns 410 Gone on the company URL. That is deliberate: Apollo’s robots.txt disallows /directory/* and lists a dedicated sitemap-410.xml, both signals that the old public company and people directory pages were intentionally retired and de-indexed. The firmographic and contact data those pages once showed now lives inside the logged-in Apollo app and its paid API. So Apollo is not challenging you, the public page genuinely no longer exists at that address.

I also checked each site’s robots.txt directly. ZoomInfo’s robots.txt disallows /search/company, /people, /people_directory, /executives, and /api, and blocks a long list of AI and SEO crawlers outright. Apollo’s allows most named crawlers on its marketing pages but disallows /directory/*. Neither robots.txt is a legal contract, but both make the owner’s intent for the contact-data surfaces explicit.

Method 1: Can you scrape ZoomInfo and Apollo with Python directly?

Not reliably with requests and BeautifulSoup, because of the challenge and the 410 shown above; a headless browser is the only DIY path that has a chance, and only against ZoomInfo. BeautifulSoup parses HTML you already have, it cannot execute Cloudflare’s challenge script, so requests plus BeautifulSoup dies at the 403 on ZoomInfo and at the 410 on Apollo. If you read my Beautiful Soup guide, the parsing step is the easy part, getting an unblocked HTML response is the wall here.

The only self-hosted approach with any chance against ZoomInfo is a real browser engine via Playwright, which can run the challenge JavaScript. The skeleton looks like this:

# pip install playwright && playwright install chromium
from playwright.sync_api import sync_playwright

URL = "https://www.zoominfo.com/c/zoominfo-technologies-llc/364098188"

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(URL, wait_until="domcontentloaded", timeout=60000)
    # Cloudflare challenge may need several seconds to resolve.
    page.wait_for_timeout(8000)
    title = page.title()
    print("Title:", title)
    if "Just a moment" in title:
        print("Still on the Cloudflare challenge page.")
    browser.close()

I am not pasting output for this snippet, because a default headless Chromium from a datacenter IP is exactly the fingerprint Cloudflare’s challenge is tuned to catch, so honestly reporting a result would mean reporting a block. In practice, default Playwright clears low-tier Cloudflare challenges sometimes and fails the hard ones, and ZoomInfo runs the hard tier. Making it work consistently means adding stealth patches, a residential IP, and challenge-solving logic, at which point you are rebuilding a scraper API by hand. For the techniques involved, see my guide on scraping without getting blocked. For Apollo there is no DIY page path at all: the public URL is 410, so the only routes are the logged-in app (which needs an account and breaches the terms more directly) or Apollo’s official API.

If you want a general grounding in the request-and-parse workflow before tackling protected targets, my Python web scraping guide covers the basics on sites that actually let you in.

Method 2: How do you scrape ZoomInfo and Apollo with a scraper API?

Send the target URL to a scraper API with JavaScript rendering switched on, and it handles the Cloudflare challenge and IP rotation for you. Because both sites block the DIY path at the door, this is the realistic technical route for any public data on either one. A scraper API runs a real browser on its side, rotates residential IPs, and returns the rendered HTML, so the challenge becomes the vendor’s problem instead of yours.

ChocoData is one such service, with a universal endpoint plus a set of dedicated endpoints. The request shape is a single GET carrying your target URL and an API key, with JavaScript rendering switched on for a challenge-protected target like ZoomInfo:

import requests

API_KEY = "YOUR_API_KEY"
target = "https://www.zoominfo.com/c/zoominfo-technologies-llc/364098188"

resp = requests.get(
    "https://api.chocodata.com/api/v1/universal",
    params={
        "api_key": API_KEY,
        "url": target,
        "render_js": "true",
    },
    timeout=120,
)
print(resp.status_code)
# resp.text now holds the rendered HTML; parse it with BeautifulSoup.

I did not run this snippet, because it needs a paid key, so treat the parameter names as illustrative and confirm them against ChocoData’s own docs before you build on it. The principle holds across any reputable scraper API: you hand it the URL, it returns the rendered page from a rotating residential IP, and Cloudflare’s challenge, the JS rendering, and the rate limits become the vendor’s problem. ChocoData advertises residential proxy rotation and optional JavaScript rendering alongside 453 dedicated endpoints, which are the capabilities ZoomInfo’s challenge makes mandatory.

Two limits a scraper API does not remove. First, the login wall: a scraper API gets you past Cloudflare to the public page, but it does not log you into a ZoomInfo account, so the gated contact data (emails, direct dials) stays gated unless you supply an authenticated session, which is a much more direct terms breach. Second, the legal layer is untouched. Routing through residential proxies grants you no permission under either vendor’s terms and gives you no GDPR cover when you collect personal contact data. Use this for public, non-personal firmographics, keep individual-level contact records out of your store unless you have a lawful basis, and get counsel before any collection touches identifiable EU, UK, or California residents.

Should you scrape these sites or use their official APIs?

For ZoomInfo and Apollo specifically, the official API is usually the better answer, with scraping as a narrow fallback for public firmographics only. Here is the honest trade-off across the three routes:

RouteGets contact dataInside vendor termsEffortBest for
Official API (ZoomInfo / Apollo)Yes (paid, by credit)YesLowProduction contact enrichment
DIY scraper (requests / Playwright)No (login-walled)NoHigh, often blockedNot viable on these targets
Scraper API (ChocoData)Public firmographics onlyNo (terms unchanged)MediumPublic company data at volume

Both vendors sell API access to their own databases under contract: ZoomInfo through its Enterprise API and Apollo through a REST API on paid plans. Going through the official API keeps you inside the terms, returns structured JSON instead of a Cloudflare fight, and is the only legitimate way to reach the contact data that makes these products valuable. Scraping earns its place only when you need public firmographic data at a volume or price the API does not serve, and you have weighed the contract and data-protection risk. Whichever route you pick, the data-protection obligations on personal contact data follow you, so build the opt-out and deletion handling in from the start rather than bolting it on later.

FAQ

Can I use ZoomInfo's or Apollo's own API instead of scraping?

Yes, and for most use cases it is the right call. Both sell paid API access to their databases under contract: ZoomInfo through its Enterprise API and Apollo through its REST API on paid plans. Going through the official API means you are inside their terms, you get structured JSON instead of fighting Cloudflare, and you avoid the contract-breach risk that scraping creates. The catch is cost and rate limits, and you only get data the vendor chooses to expose. Scraping enters the picture when the official API does not cover what you need or the pricing does not work, and you accept the added legal and technical risk that comes with it.

Why does Apollo return 410 instead of 403 or a login wall?

A 410 Gone status tells crawlers the page is permanently removed, not temporarily blocked. Apollo's robots.txt disallows /directory/* and lists a dedicated sitemap-410.xml, which signals that its old public company and people directory pages were intentionally retired and de-indexed. The data those pages once showed now lives inside the logged-in Apollo app and its paid API. So a request to a /companies/ URL is not being challenged, the public page genuinely no longer exists at that address.

Is scraping B2B contact data legal under GDPR?

A business email or direct dial of a named person is personal data under GDPR, so the regulation applies even though it is work contact information. You need a lawful basis to process it, most often legitimate interest, plus you must inform the data subject and honor deletion and objection requests. Both ZoomInfo and Apollo run opt-out and data-subject-request processes for exactly this reason. Scraping their databases and reusing the contacts does not transfer any lawful basis to you, you become a separate controller with your own obligations. Get legal advice before processing identifiable EU or UK residents at scale.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.