Apollo is a prospecting database. Your agent searches it for people and companies matching your brief, then reveals emails and phone numbers only for the ones you shortlist. Searching is free. Revealing costs credits, so hyreflow filters first and spends second.
Apollo starts the pipeline. It builds the list, then hands the shortlist to enrichment for better coverage, or straight to a sequencer.
narrow to companies that are actually hiring before you spend a reveal
waterfall the contacts Apollo could not resolve on its own
clear the addresses before a sequencer ever sees them
turn the finished list into a campaign, paused until you approve it
A skill is a whole pipeline your agent already knows. These ones call Apollo as part of the run.
Sweep Layoff Signal news, source displaced talent via GitHub + Apollo, enrich via Prospeo, push to outreach.
PredictLeads signals fresh funding. Build a candidate pool via GitHub + Apollo weeks before the JDs go live.
TheirStack flags 50+ open roles. Poach displaced talent via Apollo + Lusha into Lemlist.
Read the CV with claude, map who would actually want them via TheirStack + Apollo, name the hiring manager via Prospeo, send the anonymized profile through Instantly. Stops at the reply.
Every tool ships with a written playbook, and the agent loads it before the first call. Auth, rate limits, what each call costs, which actions need your approval, and the mistakes worth avoiding. It is the difference between an agent that knows the tool and one that guesses at it.
Read the Apollo docs →Routing layer for Apollo API work. WHAT/guardrails here; HOW in reference/endpoints.md
(59 ops) and the Python client lib/apollo.py. Full per-endpoint OpenAPI is cached at
reference/docs/apollo/raw/<slug>.md.
https://api.apollo.io/api/v1x-api-key (Apollo → Settings → API). Env APOLLO_API_KEY; never hardcode.page + per_page; response pagination:{page,per_page,total_entries,total_pages} + a result array (people/contacts/accounts/organizations). Client _paginate follows it.search_people / search_organizations are net-new prospecting — they return no emails/phones and cost no credits. Use them to build shortlists.enrich_person (/people/match), bulk_enrich_people, enrich_organization reveal emails/phones and consume credits. reveal_personal_emails / reveal_phone_number flags gate paid reveals — default off, confirm before bulk.add_contacts_to_sequence enrolls people into live sending and activate_sequence starts it — approval-gated (pilot one, confirm, then batch).People (search, enrich, bulk_enrich), organizations (search, enrich, bulk_enrich, get, job_postings),
contacts (create, update, view, search, bulk_create), accounts (create, update, view, search),
sequences (search, add_contacts, activate, deactivate), lookups (fields, email_accounts, users, lists).
Anything else in the 59-op surface → generic request(method, path, …) + reference/endpoints.md.
Apollo is a sourcing + enrichment engine. Canonical flow: search_people/search_organizations
to build a net-new shortlist (free) → enrich_person only on the shortlist (credits) → write to
your ATS (recruit-crm) or hand to a sequencer (instantly/sourcewhale). Enrich late, on the filtered
set, never the whole funnel (over-provision then filter).
Field-shape note: these are vendor-native operational notes. The client returns the raw vendor JSON and uses the method names in this file — read field shapes accordingly (no normalized-wrapper /
result.data.prefix).
Response shape (our direct client): people search returns top-level { total_entries, people, pagination } - read response["people"]. Company search is canonical at organizations (not accounts) - read response["organizations"]; rows carry name, primary_domain/domain, linkedin_url, industry.
mixed_people/api_search = free preview (obfuscated names/contact gaps, no credits) - use for cheap discovery/shortlisting. mixed_people/search = paid (full coverage). Start free, escalate to paid only when needed.S****, K., -, N/A, masked strings from free search; do not feed them to email-finders/pattern generators. Bridge: free search -> people match by Apollo id -> use the resolved last_name. Flag last_name_obfuscated=true.include_similar_titles=true unless strict matching is asked. Prefer q_organization_domains_list/organization_ids over name-only keywords. For exact industry use organization_industry_tag_ids (taxonomy IDs from industry_tag_id/industry_tag_hash in org rows - Apollo doesn't publish the taxonomy in the spec), not q_organization_keyword_tags (keyword search, pulls off-industry).organization_num_employees_ranges is contract-sensitive - use the exact format Apollo expects; don't trust pagination.total_entries alone - pilot with low per_page first.lib/apollo.pyImport: from lib.apollo import Apollo → instantiate Apollo() (reads key from env). Base: https://api.apollo.io/api/v1. Generic passthrough: request(method, path, *, params, json).
activate_sequence(sequence_id: str) -> dict — POST /emailer_campaigns/{id}/approve — starts the sequence sending.add_contacts_to_sequence(sequence_id: str, contact_ids: list[str], email_account_id: str, **opts) -> dict — POST /emailer_campaigns/{id}/add_contact_ids — ENROLLS contacts into live outreach.bulk_create_contacts(contacts: list[dict], **opts) -> dictbulk_enrich_organizations(domains: list[str], **opts) -> dict — POST /organizations/bulk_enrich — up to 10 domains per call.bulk_enrich_people(details: list[dict], **opts) -> dict — POST /people/bulk_match — enrich up to 10 people per call. Consumes credits.create_account(payload: dict) -> dictcreate_contact(payload: dict) -> dict — POST /contacts — create a CRM contact in your Apollo.deactivate_sequence(sequence_id: str) -> dictenrich_organization(domain: str, **params) -> dict — GET /organizations/enrich — enrich one company by domain.enrich_person(payload: dict) -> dict — POST /people/match — enrich one person (email/phone). Consumes credits.get_organization(org_id: str) -> dictlist_email_accounts(**params) -> Any — GET /email_accounts — sending mailboxes (need an id to enroll into sequences).list_fields(**params) -> Anylist_lists(**params) -> Any — GET /labels — Apollo lists.list_users(**params) -> Anyorganization_job_postings(org_id: str, **params) -> Anysearch_accounts(filters: dict | None = None) -> Iterator[dict]search_contacts(filters: dict | None = None) -> Iterator[dict] — POST /contacts/search — search your Apollo contacts.search_organizations(filters: dict | None = None) -> Iterator[dict] — POST /mixed_companies/search — company/account search.search_people(filters: dict | None = None) -> Iterator[dict] — POST /mixed_people/api_search — prospect net-new people. No emails/phones, no credits.search_sequences(filters: dict | None = None) -> Iterator[dict] — POST /emailer_campaigns/search.update_account(account_id: str, payload: dict) -> dictupdate_contact(contact_id: str, payload: dict) -> dict — PATCH /contacts/{contact_id}.view_account(account_id: str) -> dictview_contact(contact_id: str) -> dict