Exa Documentation Audit
The docs are deep, agent-aware, and unusually well-instrumented (real OpenAPI, a thorough error reference, an llms.txt index) — but they describe two different products: one where a May 1, 2026 deprecation already happened, and one where none of it did. As of today (2026-06-06) both versions are live, and an AI coding agent reading them straight will integrate against endpoints, fields, and search modes that no longer exist.
1. The /research endpoint is removed per Exa's own notice but still documented as live across four pages (critical)
Location: /changelog/may-2026-api-deprecations vs /reference/getting-started, /llms.txt, /reference/research/overview, /reference/rate-limits
Problem: The deprecation notice states: "Status as of May 1, 2026: these removals are complete. The fields, parameters, and endpoints below no longer exist… /research endpoint — Sunsetting on May 1." Today is 2026-06-06 — past that date. Yet getting-started still presents /research as one of "four core functionalities"; llms.txt still lists the Research API and every /research endpoint (create-a-task, get-a-task, list-tasks) as current docs; rate-limits still lists /research/v1; and research/overview says the API "is being deprecated on May 1, 2026" (future tense for a date already gone) with the remedy "Migrate to /search with type: "deep-reasoning"."
Consequence: A developer or agent following the welcome page or the llms.txt index builds an integration against an endpoint that now returns 404. The future-tense framing on research/overview tells readers they still have time to migrate when the window has already closed.
The fix: Run a single deprecation sweep. Remove /research from getting-started's core list, the llms.txt index, and rate-limits; change research/overview to a past-tense removal banner with the migration target front and center.
2. Removed response fields and request parameters are still documented as working in the OpenAPI spec and SDK reference (critical)
Location: /reference/search (OpenAPI), /sdks/python-sdk-specification vs /changelog/may-2026-api-deprecations
Problem: The deprecation notice removes resolvedSearchType, highlightScores, startCrawlDate, and endCrawlDate (null on April 15, removed May 1). But the /search OpenAPI still documents startCrawlDate/endCrawlDate as working filters ("Results will include links that were crawled after this date"), still emits resolvedSearchType in the response example, and SearchResultOutput still documents highlightScores.
Consequence: Agents parse machine-readable specs literally. An agent reading the OpenAPI will write code that filters on startCrawlDate (per the notice this was "silently ignored" as of April 15; after the May 1 "hard removal" the notice only says removed features "stop working," so it may now be ignored or rejected — the docs don't say which, and a silently-ignored filter is the worst case: requests succeed but the filter does nothing) and reads resolvedSearchType/highlightScores (now null), producing silent data bugs with no error to catch.
The fix: Regenerate the /search OpenAPI from the post–May 1 schema. Drop the four removed fields/params, or mark them deprecated: true with an explicit "removed — ignored/returns null" description that states the post–May 1 behavior precisely.
3. The prescribed fix for deprecated scores points to a search mode the API no longer lets you select (critical)
Location: /changelog/auto-keyword-score-deprecation vs /reference/search, /sdks/python-sdk-specification, /reference/error-codes
Problem: The score-deprecation changelog tells affected users: "Neural search: Scores continue to work exactly as before with no changes… If your application relies on scores from Auto search, you should migrate as soon as possible." But neural is not in the /search type enum (only instant, fast, auto, deep-lite, deep, deep-reasoning), not in the SDK SearchType Literal, and the error-codes validation message confirms neural/keyword are unselectable.
Consequence: A developer who relies on relevance scores follows the official migration path, tries type="neural", and gets a validation error. The remedy the docs prescribe is impossible to execute — there is no supported way to keep scores.
The fix: Either restore neural as a selectable type (and add it to the enum + SDK Literal), or rewrite the changelog to give a remedy that actually works (e.g., which current type returns score, or state plainly that scores are gone).
4. Highlights were removed from the Python SDK, but the Python quickstart, cheat sheet, and SDK page all recommend them as the default (critical)
Location: /reference/quickstart, /sdks/cheat-sheet, /sdks/python-sdk vs /changelog/sdk-major-version-changes, /changelog/highlights-restored-js-sdk
Problem: The SDK changelog says "The highlights feature has been completely removed from all SDKs," and the restoration note clarifies "This update applies only to the JavaScript SDK… Other SDKs can access highlights via direct API calls." Yet the Python SDK page lists "Prefer contents={"highlights": True} for first integrations" as a Recommended default, the first-run quickstart leads with exa.search(..., contents={"highlights": True}), and the cheat sheet uses get_contents(["ids"], highlights=True).
Consequence: A brand-new Python user — the exact audience of a quickstart — copies the single most-recommended Python pattern, which per the two changelogs should not work on the Python SDK (highlights removed from all SDKs, restored only in JS). The audit can't execute the snippet against the installed package, so the precise failure mode (error vs. silently-empty highlights) needs confirming — but either way the docs' top Python recommendation contradicts the platform's own migration guidance.
The fix: Reconcile Python examples with the SDK reality. If highlights are gone from the Python SDK, switch Python quickstart/cheat-sheet examples to the API-direct approach and drop the "recommended default" framing; if they were quietly restored to Python too, document that restoration the way JS got its own changelog.
5. The same search type has three different latency numbers depending on which page you read (significant)
Location: /reference/search-api-guide, /changelog/instant-search-launch, /changelog/new-fast-search-type
Problem: fast is listed as ~450 ms (search-api-guide), ~500ms (instant changelog comparison table), and "p50 latency below 425ms" (fast changelog). instant is ~250 ms (guide) but "sub-200ms" (instant changelog description), "Sub-150ms" (same page's body), and "sub-150ms" (same page's table) — three figures on one page. deep is 4–15 seconds (guide) vs ~5s (instant changelog).
Consequence: A developer choosing a type for a latency-sensitive app (voice, autocomplete) can't trust any single number; the canonical "choose a search type" guide disagrees with the launch announcements, and one announcement disagrees with itself.
The fix: Pick one source of truth (the search-api-guide table), generate it once, and have changelogs link to it rather than restate numbers. Fix the three-way instant figure on the launch page first.
6. The Python SDK spec's return example shows a score field that the deprecation notice and response schema both say doesn't exist (significant)
Location: /sdks/python-sdk-specification vs /changelog/auto-keyword-score-deprecation, /reference/search
Problem: The Python SDK specification's return example shows "score": 0.92 on a default (auto) search. But the score-deprecation changelog says "Auto search: The score field will no longer be returned," and the /search SearchResultOutput schema has no score field at all.
Consequence: A developer writing code against the SDK example reads result.score, gets None/KeyError on a real auto search, and has no way to reconcile it because the schema never mentions score.
The fix: Remove score from the auto-search example, or replace it with a search type that actually returns it (and confirm such a type is selectable — see issue 3).
7. The SDK's Category allows pdf, but the API's category enum rejects it (significant)
Location: /sdks/python-sdk-specification vs /reference/search (OpenAPI)
Problem: The Python SDK Category Literal is ['company', 'research paper', 'news', 'pdf', 'personal site', 'financial report', 'people']. The /search OpenAPI category enum omits pdf: [company, research paper, news, personal site, financial report, people].
Consequence: A developer who sees pdf in the SDK type signature passes category="pdf", and the request fails validation at the API. The SDK's own type hints advertise a value the backend won't accept.
The fix: Align the two enums — add pdf to the API enum if supported, or remove it from the SDK Literal if not.
8. Changelog examples call search_and_contents, a method the Python SDK reference never documents (significant)
Location: /changelog/new-fast-search-type vs /sdks/python-sdk, /sdks/python-sdk-specification
Problem: Changelog examples use exa.search_and_contents(...) / exa.searchAndContents(...). The current Python SDK reference (/sdks/python-sdk) and the Python SDK specification document exa.search(...) and exa.get_contents(...) — not search_and_contents. (The JavaScript SDK page in the evidence is truncated at "## Quick Start," so this finding is scoped to the Python reference, which is fully captured and clearly omits the method.)
Consequence: A developer copies a Python changelog snippet, calls search_and_contents, and either hits an AttributeError or has to guess whether the method was renamed. The copy-paste path from changelog to working Python code is broken.
The fix: If the method exists in the Python SDK, document it on the SDK reference page; if it was renamed to search(..., contents=...), update the Python changelog examples to the current method.
9. The rate-limits reference covers four endpoints, lists a removed one, and omits documented newer APIs (significant)
Location: /reference/rate-limits
Problem: The page documents limits only for /search (10 QPS), /contents (100 QPS), /answer (10 QPS), and /research/v1 ("legacy/deprecated", 15 concurrent tasks) — the last of which no longer exists per issue 1. It documents no limits for the Agent API (/reference/agent-api/overview) or the OpenAI-compatible /chat/completions flow (/reference/quickstart), both of which the docs present as usable.
Consequence: A developer building on the Agent API or the chat-completions endpoint has no way to know their rate ceiling and can't capacity-plan; meanwhile the one limit that is precise (/research/v1) is for a dead endpoint.
The fix: Drop /research/v1, and add rows for the Agent API and /chat/completions — or state explicitly that those endpoints are unlimited/governed differently.
10. Exa Agent and the (removed) Research API overlap with no guidance on which to use (significant)
Location: /reference/agent-api/overview vs /reference/research/overview, /changelog/may-2026-api-deprecations
Problem: Both APIs are described in near-identical terms — Research runs "asynchronous, multi-step research tasks that… synthesize findings, and return structured results with citations"; Agent runs "asynchronous, multi-step web research… with… structured outputs, and citations." The deprecation notice routes Research users to /search with type: "deep-reasoning" — a third option — while Agent (beta, requiring Exa-Beta: agent-2026-05-07) sits alongside, unmentioned by the migration. The two sibling APIs even spell the same status differently: Agent uses "cancelled", Research uses "canceled".
Consequence: A Research API user whose integration just broke has to choose between /search deep-reasoning (per the notice) and the Agent API (which looks like the natural successor) with zero guidance. The inconsistent status spelling means anyone string-matching on lifecycle status across both APIs will miss cases.
The fix: Add a short "which API should I use" decision section covering Search/deep-reasoning vs Agent vs the retired Research API, and standardize the lifecycle status spelling across sibling APIs.
11. The MCP deprecation table routes two tools to the removed Research API, and the page never states its free-tier limits (significant)
Location: /reference/exa-mcp vs /changelog/may-2026-api-deprecations
Problem: The MCP page's deprecation table routes deep_researcher_start/deep_researcher_check to the Research API — linking /reference/research/create-a-task and /get-a-task, the exact endpoints removed in issue 1. Separately, the page advertises a "generous free plan" ("Exa MCP has a generous free plan") but never states the actual free-tier limits anywhere on the page.
Consequence: A developer migrating off the deprecated deep_researcher_* MCP tools is pointed at Research API endpoints that now 404, with no working successor named. And anyone evaluating the free plan can't find the numbers that determine whether they need to bring their own key.
The fix: Re-point deep_researcher_* to a live successor (Agent or /search deep-reasoning), and publish the actual free-tier limits on the MCP page rather than only describing the plan as "generous."
12. The "Exa's Capabilities Explained" page advertised in the index serves the search guide instead (minor)
Location: /reference/exas-capabilities-explained (and its llms.txt index entry)
Problem: The llms.txt index advertises this URL as a page that "explains some of the available feature functionalities of Exa and some unique ways you might use Exa." Both web_fetch and TinyFish confirm the page actually served is the Exa Search API guide (H1 "Exa Search API", first section "Welcome", "Why choose Exa?"). The described capabilities page is effectively missing.
Consequence: For a human this is a shrug — they read the search guide instead. The real cost is to agent indexing: an agent walking the llms.txt index fetches this URL expecting a distinct capabilities/use-cases overview, gets a duplicate of the search guide, and wastes the index slot while the promised content stays undiscoverable.
The fix: Either restore the real capabilities page at this URL or remove the entry from llms.txt so the index doesn't promise content that isn't there.
13. The cheat sheet's get_contents examples pass a literal placeholder instead of real IDs (minor)
Location: /sdks/cheat-sheet
Problem: The contents examples are results = exa.get_contents(["ids"]) and exa.get_contents(["ids"], highlights=True) — the string "ids" is a placeholder presented as runnable code, not marked as a variable to replace.
Consequence: An agent extracting this snippet runs it verbatim and sends the literal string "ids" as a document identifier, getting an empty or error response with no signal that a substitution was required.
The fix: Use a real, obviously-illustrative value (e.g., a full URL) or clearly mark the placeholder, e.g. exa.get_contents(["https://example.com/article"]).
14. x402 and the pricing changelog present prices in different units, and deep-lite pricing exists in only one of them (minor)
Location: /reference/x402-guide vs /changelog/pricing-update
Problem: The x402 page prices per request ($0.007, deep $0.012, deep-reasoning $0.015), while the pricing changelog prices per 1000 requests ($7/1000, Deep $12/1000, Deep Reasoning $15/1000) — same values, different units, with no cross-reference. The changelog never mentions deep-lite pricing at all, while x402 lists deep-lite at $0.012 — identical to deep, despite the two being presented as different tiers elsewhere.
Consequence: A developer comparing costs has to mentally convert units to confirm the prices match, and gets no answer for what deep-lite costs on the standard (non-x402) plan.
The fix: Use consistent units across both pages (or cross-link them), and add deep-lite to the standard pricing table — clarifying why deep-lite and deep cost the same under x402 if they're distinct tiers.
15. The OpenAI-compatible chat-completions flow isn't cross-linked from the SDK reference (minor)
Location: /reference/quickstart vs /sdks/python-sdk, /reference/rate-limits
Problem: The quickstart shows an OpenAI client pointed at base_url="https://api.exa.ai" with model="exa" — an OpenAI-compatible /chat/completions flow. Nothing in the SDK reference pages links to or explains this flow, and (per issue 9) the rate-limits page publishes no limit for it.
Consequence: A developer who wants to use Exa through an OpenAI-compatible client has to stumble onto it in the quickstart; there's no path from the SDK reference, and no documented rate ceiling once they find it.
The fix: Cross-link the chat-completions flow from the SDK reference pages, give it its own short reference section (base URL, model value, supported params), and add its rate limit to the rate-limits page.
16. x402 silently clamps numResults to 10 (minor)
Location: /reference/x402-guide
Problem: The x402 guide states: "x402 requests are capped at 10 results maximum. If you request more than 10, numResults is silently clamped to 10 and pricing is based on 10 results."
Consequence: This is the same silent-surprise class the audit flags as severe elsewhere: a developer who passes numResults=25 over x402 gets only 10 results back with no error and is billed accordingly. Code written against the standard API's numResults behavior quietly returns a truncated set when routed through x402.
The fix: Surface the clamp prominently wherever numResults is documented (not only on the x402 page), and consider returning a warning field in the response so the truncation isn't invisible.
What they do well
- The error-codes reference is genuinely strong — structured error responses with
requestId/tag, and specifics like the 100-result cap tied to highlights and thelivecrawl+maxAgeHoursconflict. - Real machine-readable surface — a published
/searchOpenAPI spec and an llms.txt index mean agents can discover the API programmatically; the problem is drift, not absence. - Explicitly agent-aware — the docs openly address coding agents as a first-class audience, ahead of most API docs. One caveat worth naming: the canonical search guide opens with a "STOP. Do not attempt to build the integration from scratch using this page" block aimed at coding agents — sensible if it routes them to the MCP/SDK path, but it sits in tension with treating that same page as the agent's reference, so the intended agent journey should be made explicit rather than implied.
Top 3 recommendations
- Run one post–May 1 deprecation reconciliation pass. The
/researchendpoint and the removed fields (resolvedSearchType,highlightScores,startCrawlDate/endCrawlDate) are still live in getting-started, llms.txt, rate-limits, the/searchOpenAPI, and the SDK spec. Regenerate every reference page from the actual current API so docs and reality agree on what exists — and state the precise post–May 1 behavior (ignored vs. rejected) for the dropped request params. - Fix the impossible migration paths. Restore
neuralas a selectable type or rewrite the score-retention guidance; align Python's highlights examples (quickstart, cheat sheet, SDK page) with the "removed from SDK" changelog; reconcile thepdfcategory andscorefield between SDK and API; and re-point the MCPdeep_researcher_*tools off the removed Research API. - Make latency, pricing, and method names single-sourced. Generate the search-type latency/price table once and have changelogs link to it instead of restating divergent numbers; remove undocumented methods (
search_and_contents) and placeholder code (["ids"]) from copy-paste examples; and surface the silent surprises (x402'snumResultsclamp) wherever the affected parameter is documented.