Apollo Documentation Audit
One-line summary: Apollo's developer docs are broad and genuinely agent-oriented (llms.txt, a published OpenAPI spec, MCP, a CLI), but the load-bearing reference pages — authentication, status codes, rate limits, pricing — defer their actual content elsewhere; the auth model is split three ways with no page that reconciles it; one search response ships a field whose type isn't stable; and a 210M-vs-230M database-size figure disagrees across the spec and every human-facing surface.
1. The Authentication reference page never names the header or the master key (critical)
Location: /reference/authentication
Problem: The entire Authentication reference reads: "You need to create an API key… The API key should be included in the header of your API request." It never names the header (x-api-key), never shows an example request, and never mentions the "master API key" concept. The header name appears only inside the OpenAPI spec embedded on each endpoint page ("securitySchemes": { "apiKey": { … "name": "x-api-key" } }), the master-key requirement is stated only on individual endpoints (People API Search: "This endpoint requires a master API key"), and the master-key definition lives only in a how-to (/docs/create-api-key).
Consequence: A developer who lands on the page literally titled "Authentication" cannot construct a working request from it — they don't learn the header name, and they don't learn that endpoints like People API Search and "Get a List of Users" require a master key rather than an ordinary one. The single most important auth distinction in the API (regular key vs. master key) is absent from the page every endpoint links to for auth.
The fix: On the Authentication page, state the header name explicitly (x-api-key: <your key>), show a complete curl example, and define the master key inline — what it is and which endpoints require it. Don't rely on readers scraping the embedded spec.
2. The Status Codes / Errors page contains no status codes — and the FAQ routes error-debugging here (critical)
Location: /reference/status-codes, /docs/apollo-api-faqs
Problem: The entire "Status Codes / Errors" page is a procedure for finding codes elsewhere: "Go to the reference documentation for an endpoint… click the EXAMPLE drop-down, then select a status code." It lists zero actual codes, zero error-body shapes, and zero error messages, and concedes "Not all API status codes are currently documented by Apollo." Worse, the FAQ's answer to "What does this API error mean?" sends developers straight here: "Apollo provides status codes for example responses in the API reference docs. Check out Status Codes / Errors to learn how to find the status codes." So the page developers are explicitly funneled to when an error occurs is itself contentless.
Consequence: There is no centralized error reference. A developer debugging a 403 or 422 in production follows the FAQ to a page that tells them to hunt through per-endpoint dropdowns — and the docs concede the list is incomplete. Agents and error-handling code have nothing structured to parse, so error handling becomes guesswork plus a support ticket.
The fix: Publish a real error reference: a table of HTTP status codes the API returns, the JSON error body schema, and the common application-level error messages with causes. The documented 422 failure mode for Bulk Update Accounts (async + account_attributes) is a concrete example that belongs in a central table, not buried in one endpoint's description. Point the FAQ at that table.
3. The Rate Limits page lists no rate limits (critical)
Location: /reference/rate-limits
Problem: The entire Rate Limits page tells you to call an endpoint to discover your limits ("call the View API Usage Stats and Rate Limits endpoint") and gives only a hypothetical number: "if your team's rate limit is 200 requests per minute… (As an example)." There are no per-plan tiers, no per-endpoint numbers, and the page documents no rate-limit response headers a client would read to back off.
Consequence: You cannot design a client's retry/backoff strategy from the docs — you must first make an authenticated call to learn your own limits, which is impossible during pre-integration planning or when evaluating the API before signing up. With no documented response headers to read, developers can't implement adaptive throttling and will hit limits blind.
The fix: Document the actual fixed-window limits per plan tier (even as ranges), and document the rate-limit response headers the API returns so clients can self-throttle. Keep the live endpoint as the source of truth for exact current values, but stop making it the only source.
4. Example response mixes boolean and string types for sibling fields (critical)
Location: /docs/find-people-using-filters (People API Search example response)
Problem: In the documented 200 response, has_email and has_city are booleans (false/true), but the sibling field has_direct_phone is a string: "Yes" in one record and "Maybe: please request direct dial via people/bulk_match" in another. The page's "key elements" table documents only title and organization — not the has_* fields and not last_name_obfuscated ("Po***r").
Consequence: This is the one finding that breaks at runtime, not just on read. A client that treats has_* as a uniform boolean shape deserializes fine on has_email and then breaks on has_direct_phone, which can be either "Yes" or a ~50-character instruction sentence. Strongly-typed clients and the generated OpenAPI SDK will crash or coerce incorrectly, and nothing in the documented field table warns that has_direct_phone is special.
The fix: Make has_direct_phone a boolean and move the "request via people/bulk_match" guidance into prose or a separate field, OR document it explicitly as a string/enum with its possible values. Either way, add has_* and last_name_obfuscated to the field table and ensure the OpenAPI schema matches the real types.
5. The auth model is split three ways with no page that reconciles it (significant)
Location: /reference/authentication, /reference/people-api-search, /docs/apollo-mcp, /docs/apollo-cli-overview
Problem: Apollo ships at least three auth models and no single page maps them to surfaces. The REST API uses an x-api-key header (named only inside the embedded OpenAPI spec); most REST endpoints additionally require a master key; but the MCP server states "API key required: No" and uses OAuth 2.0, the CLI says "Apollo CLI uses OAuth 2.0 — no API key needed," and partner integrations use OAuth 2.0 only. The Authentication page mentions only "an API key" (for users) and OAuth (for partners) — it never tells you the MCP and CLI surfaces don't use API keys at all.
Consequence: A developer adopting more than one Apollo surface (REST + MCP, or REST + CLI) must reverse-engineer the auth scheme for each from a different page, because nothing states "REST = x-api-key / master key; MCP, CLI, partners = OAuth." Picking the wrong scheme for a surface fails authentication, with no centralized guide to correct it.
The fix: Add an auth-model matrix to the Authentication page — surface (REST / MCP / CLI / partner) × scheme (API key, master key, OAuth) — and link each endpoint and surface to the relevant row.
6. Contact-database size disagrees across the spec and every human-facing surface (significant)
Location: /docs/apollo-api-overview, /reference/people-api-search (embedded spec), /reference/openapi-specification, /docs/apollo-mcp, /docs/apollo-cli-overview, www.apollo.io/product/api
Problem: The API Overview claims a database "of over 210 million contacts and 35 million companies," and the served OpenAPI spec's description hard-codes the same "over 210 million contacts and 35 million companies." But the MCP page advertises "Apollo's 230M+ contact database," the CLI page says "Search 230M+ contacts," and the public product page says "230M+ contacts." The same figure is stated two different ways depending on which surface you land on.
Consequence: No API call fails because of this, so it's a cross-surface accuracy/trust problem rather than a production failure — but the figure baked into the machine-readable OpenAPI description (the one tools and agents actually parse) is the lower 210M, so the canonical source disagrees with every human-facing surface. An agent answering "how big is Apollo's database?" from the spec will report 210M while the MCP and marketing pages say 230M.
The fix: Pick one figure, source it to a single dated definition (e.g. "230M+ contacts as of <date>"), and template it into the OpenAPI description, the API Overview, MCP, CLI, and the product page so they update together. Stop hand-copying the number into prose.
7. The API Pricing page contains no prices, and it contradicts the FAQ on plan access (significant)
Location: /docs/api-pricing, /docs/apollo-api-faqs, /docs/apollo-api-overview
Problem: The "API Pricing" page has no prices, no plan names, and no credit amounts — it links to a marketing pricing page and to a login-gated "About Credits" page ("login required"). It also states "More advanced API access is dependent on your organization's current Apollo plan," and the API Overview separately says "Your organization's API access is restricted by your current Apollo plan." Yet the FAQ asserts "all Apollo plans include access to our API." The Pricing page contradicts the FAQ on the same docs site.
Consequence: A developer evaluating cost cannot find a single credit price or plan boundary without logging in, and the one quotable claim they can find ("all plans include access") is contradicted by the plan-restriction language on the Pricing page and Overview — so they can't tell whether their plan can actually call the endpoints they need.
The fix: Put at least the credit-consumption model and per-plan API access boundaries on the public pricing page (no login). Reconcile "all plans include access" with the plan-restriction language — state precisely what every plan gets vs. what requires an upgrade.
8. OAuth code sample is labeled Python but isn't, and the refresh example is missing (significant)
Location: /docs/use-oauth-20-authorization-flow-to-access-apollo-user-information-partners
Problem: The page introduces its OAuth sample with "The following is an example of a Python request:" — but the captured page shows the accompanying fenced block tagged Python while containing Node.js/JavaScript (e.g. const formData, request.post(...)), and the token-refresh section repeats the same "example of a Python request" lead-in with no code block after it. (Those code identifiers come from the page capture rather than text reproduced in this evidence set, so treat the "it's actually JavaScript" detail as observed-in-capture; the verifiable core is that the sample is labeled Python and the refresh example is absent.)
Consequence: A partner implementing the OAuth flow copies a snippet labeled "Python" that won't run in Python, and for the token-refresh step gets a heading promising an example that isn't there. OAuth is the only auth path for partner integrations, MCP, and the CLI — surfaces with no API-key fallback — so a broken example here blocks onboarding for all of them.
The fix: Pick one language and make the code-fence tag match the prose; add the missing refresh-token code block (or remove the dangling "following is an example" line).
9. Unresolved doc: placeholder links leaked into published pages (significant)
Location: /reference/people-enrichment, /llms.txt (Bulk People Enrichment entry)
Problem: ReadMe-style cross-reference placeholders shipped to the live .md output instead of resolving to URLs: [Native webhook response details](doc:retrieve-mobile-phone-numbers-for-contacts#webhook-response-details), (doc:enrich-phone-and-email-using-data-waterfall#response-details), and [Poll Webhook Result endpoint](doc:poll-webhook-result). The same broken doc: links also appear in the Bulk People Enrichment entry inside llms.txt.
Consequence: These render as dead/literal references — a reader or agent following the webhook documentation hits doc:poll-webhook-result instead of a real URL and cannot reach the polling endpoint docs needed to retrieve asynchronous enrichment results. Because the same breakage is in llms.txt, AI tools indexing the docs inherit the dead links too.
The fix: Resolve the doc: slugs to absolute https://docs.apollo.io/... URLs in both the rendered reference pages and the generated llms.txt. Add a build check that fails publishing if any doc: placeholder survives into output.
10. Bulk batch limits are inconsistent across parallel endpoints, and one rule contradicts itself (significant)
Location: /reference/bulk-update-contacts, /reference/bulk-update-accounts (via /llms.txt)
Problem: Bulk Update Contacts caps at "up to 100 contacts per request," while the parallel Bulk Update Accounts allows "up to 1000 accounts per request" — a 10× difference between two near-identical endpoints. Worse, the Contacts page contradicts itself: "You can update up to 100 contacts per request. For larger batches, the system will process the updates asynchronously" — implying batches above 100 are in fact accepted.
Consequence: A developer who builds a batching layer around 1000 (the Accounts number) gets failures on Contacts, and the "for larger batches… asynchronously" sentence tells them larger batches are allowed when the stated cap is 100. They can't tell whether 100 is a hard limit or a sync/async threshold.
The fix: State each endpoint's hard cap unambiguously and explain why they differ (or align them). Rewrite the Contacts sentence to separate the hard limit from async behavior — e.g. "Maximum 100 per request" — without implying >100 is accepted.
11. OpenAPI spec claims "always latest" but is statically versioned 1.0 (significant)
Location: /reference/openapi-specification (served apollo-rest-api.json)
Problem: The page promises "the download URL always reflects the latest version of the API" and the spec is "regenerated whenever these docs are published." Yet the served spec's info.version is a static "1.0", and its description hard-codes "over 210 million contacts" — the same stale figure contradicted by the MCP/CLI/product pages. The SDK quickstart also points an unpinned openapi-generator at the spec (openapi-generator generate -i … -g typescript-fetch -o ./apollo-client).
Consequence: Tooling that keys off info.version (SDK regeneration, change detection, agent caching) sees a version that never changes, so it can't tell when the API actually changed. The unpinned generator means the documented codegen path isn't reproducible over time.
The fix: Bump info.version on every regeneration (date or semver), template the contact-count figure into the description instead of hard-coding it, and pin the generator version in the quickstart.
12. Active tutorials and the FAQ link to the legacy readme.io reference host (significant)
Location: /docs/find-people-using-filters, /docs/apollo-api-faqs
Problem: Two live pages point readers to the old reference host: the tutorial says "Apollo's API reference docs [https://apolloio.readme.io/reference]" and the FAQ answers "Yes! Apollo provides API reference docs for its endpoints here [https://apolloio.readme.io/reference]." Both rely on a 301 redirect to docs.apollo.io rather than linking the canonical host directly.
Consequence: Relying on a 301 from a deprecated domain is fragile — if the redirect is ever retired, both the primary tutorial and the FAQ's "where are the reference docs" answer break at once. It also signals to agents (which often don't follow cross-domain redirects) that the reference lives on a different, now-defunct host.
The fix: Replace both links with canonical https://docs.apollo.io/reference/... URLs and grep the docs for any remaining apolloio.readme.io references.
13. Master key is defined only in a buried how-to, whose steps are mis-numbered (significant)
Location: /docs/create-api-key
Problem: The master-key concept — a toggle that grants a key access to all endpoints, and the prerequisite for endpoints like "Get a List of Users" — is defined only here, inside a how-to, despite being required by most endpoints. The how-to's steps are also mis-numbered: they go 1, 2, 4, 5, 6 with no step 3.
Consequence: Developers learn about the single most consequential key setting (master vs. scoped) only if they happen to open the "Create an API Key" how-to, not from the Authentication reference (see Finding 1). The skipped step number also makes readers wonder whether an instruction was dropped between "Click API Keys > Create new key" and "Enter a name and a description."
The fix: Surface the master-key definition on the Authentication reference page and link to it from every endpoint that requires it. Renumber the steps 1–5 consecutively.
14. Search endpoint paths use inconsistent, undocumented naming (minor)
Location: /docs/find-people-using-filters, /docs/api-pricing
Problem: Two of the most-used search endpoints follow asymmetric path conventions. People Search is POST /api/v1/mixed_people/api_search (tutorial), while Organization Search is listed as v1/mixed_companies/search (Pricing page) — mixed_people vs mixed_companies, and, more confusingly, api_search vs plain search. Nothing documents why People Search carries the api_ prefix and Org Search does not.
Consequence: A developer (or agent) generalizing the People Search path to find organizations will guess mixed_companies/api_search and get an error; the inconsistency isn't explained anywhere, so the only way to learn the correct Org path is to find its own reference page.
The fix: Document the path convention explicitly, or alias the endpoints to a consistent scheme (.../search or .../api_search for both).
15. llms-full.txt 404s on an explicitly agent-friendly docs site (minor)
Location: https://docs.apollo.io/llms-full.txt
Problem: /llms.txt resolves and indexes all guides, references, and pages, but the conventional aggregate /llms-full.txt returns HTTP 404 (page_not_found). The docs market themselves as AI-agent-friendly (llms.txt, OpenAPI, MCP, CLI), yet the full-text variant many agents probe for is absent.
Consequence: Agents that fetch llms-full.txt to ingest all docs in one request get a 404 and must crawl the per-page .md files individually, which is slower and more error-prone — undercutting the agent-first positioning.
The fix: Either publish llms-full.txt (concatenated page bodies) or, if intentionally omitted, note its absence in llms.txt so agents don't probe a dead path.
16. Deprecated endpoint still listed in the docs index with no removal date (minor)
Location: /reference/get-a-list-of-all-custom-fields (via /llms.txt)
Problem: The index still lists "Get a List of All Custom Fields" with the note "This endpoint is deprecated. To achieve the same result, use the Fields endpoint (use source: custom to get custom fields only)." It carries no sunset date and no migration urgency.
Consequence: New integrations may build against a deprecated endpoint because it's still surfaced in the index alongside live ones, then break whenever it's removed — with no documented timeline to plan around.
The fix: Add a sunset date and a one-line migration mapping (old field → Fields endpoint with source: custom), and consider collapsing the deprecated entry into the Fields page rather than listing it as a peer endpoint.
What they do well
- Genuinely agent-oriented surface area — a published OpenAPI 3 spec, a working
llms.txtindex, an MCP server, and a CLI is more machine-readable tooling than most B2B-data APIs ship. - Credit-consumption is enumerated — the API Pricing page lists exactly which endpoint families consume credits (Search and Enrichment), and reference pages note non-consuming endpoints like People API Search.
- Useful operational detail where it exists — webhook requirements (HTTPS, idempotency, retries) and the documented
422failure forasync+account_attributesare the kind of specific, real-world guidance the rest of the error docs should match.
Top 3 recommendations
- Fill the empty reference pages. Put real content on Authentication (header name, master-key definition, full example), Status Codes (a code/error-body table), and Rate Limits (per-plan numbers + response headers). These are the pages developers hit first and they currently defer everything elsewhere — and the FAQ even routes error-debugging straight to the contentless Status Codes page.
- Make the API self-consistent where machines read it. Fix the
has_direct_phoneboolean-vs-string type so generated SDKs don't crash, unify the auth model in one matrix (REST key / master key / OAuth per surface), and template the contact-count figure into the OpenAPIdescriptionwhile bumpinginfo.versionso the "always latest" claim holds. - Run a docs-build linter. Catch leaked
doc:placeholders, legacyapolloio.readme.iolinks, mis-numbered step lists, and mislabeled code-fence languages before publish — every one of these is a mechanical, automatable failure.