--- name: exa-known-pitfalls description: 'Identify and avoid Exa anti-patterns and common integration mistakes. Use when reviewing Exa code, onboarding new developers, or auditing existing Exa integrations for correctness. Trigger with phrases like "exa mistakes", "exa anti-patterns", "exa pitfalls", "exa what not to do", "exa code review". ' allowed-tools: Read, Grep version: 1.11.0 license: MIT author: Jeremy Longshore tags: - saas - exa - audit - best-practices compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # Exa Known Pitfalls ## Overview Real gotchas when integrating Exa's neural search API. Exa uses embeddings-based search rather than keyword matching, which creates a different class of failure modes than traditional search APIs. This skill covers the top pitfalls with wrong/right examples. ## Pitfall 1: Keyword-Style Queries Exa's neural search interprets natural language semantically. Boolean operators and keyword syntax degrade results. ```typescript import Exa from "exa-js"; const exa = new Exa(process.env.EXA_API_KEY); // BAD: keyword/boolean style — Exa ignores AND/OR const bad = await exa.search( "python AND machine learning OR deep learning 2024" ); // GOOD: natural language statement const good = await exa.search( "recent tutorials on building ML models with Python", { type: "neural", numResults: 10 } ); ``` ## Pitfall 2: Wrong Search Type Using neural search for exact lookups (URLs, names) or keyword search for conceptual queries silently degrades quality. ```typescript // BAD: neural search for a specific URL/identifier const bad = await exa.search("arxiv.org/abs/2301.00001", { type: "neural" }); // GOOD: keyword for exact terms, neural for concepts const exactMatch = await exa.search("arxiv.org/abs/2301.00001", { type: "keyword", }); const conceptual = await exa.search( "transformer architecture improvements for long context", { type: "neural" } ); ``` ## Pitfall 3: Expecting Content from search() `search()` returns metadata only (URL, title, score). Content requires `searchAndContents()` or `getContents()`. ```typescript // BAD: accessing .text from search() — it's undefined const results = await exa.search("AI safety research"); const text = results.results[0].text; // undefined! // GOOD: use searchAndContents for text/highlights const withContent = await exa.searchAndContents("AI safety research", { numResults: 5, text: { maxCharacters: 2000 }, highlights: { maxCharacters: 500 }, }); console.log(withContent.results[0].text); // actual content console.log(withContent.results[0].highlights); // key excerpts ``` ## Pitfall 4: Narrow Date Filters Return Empty Date filters silently exclude results. A single-day window often returns nothing without error. ```typescript // BAD: too narrow, likely returns empty array const bad = await exa.search("AI news", { startPublishedDate: "2025-03-15T00:00:00.000Z", endPublishedDate: "2025-03-15T23:59:59.000Z", }); // GOOD: reasonable window with fallback let results = await exa.search("AI news", { startPublishedDate: "2025-03-01T00:00:00.000Z", endPublishedDate: "2025-03-31T23:59:59.000Z", numResults: 10, }); // Fallback if no results if (results.results.length === 0) { results = await exa.search("AI news", { numResults: 10 }); } ``` ## Pitfall 5: findSimilar Takes a URL, Not a Query `findSimilar` expects a URL as its first argument. Passing a query string gives meaningless results. ```typescript // BAD: passing a query string to findSimilar const bad = await exa.findSimilar("machine learning research papers"); // GOOD: pass a URL — findSimilar finds pages semantically similar to it const good = await exa.findSimilar("https://arxiv.org/abs/2301.00001", { numResults: 10, excludeSourceDomain: true, }); ``` ## Pitfall 6: Date Filters with company/people Categories The `company` and `people` categories do NOT support date filters. Using them returns a 400 error. ```typescript // BAD: date filter with company category → 400 error const bad = await exa.search("AI startups", { category: "company", startPublishedDate: "2024-01-01T00:00:00.000Z", // not supported! }); // GOOD: company search without date filters const good = await exa.search("AI startups", { category: "company", numResults: 10, }); ``` ## Pitfall 7: Not Limiting Content Size Requesting full text without `maxCharacters` can return massive payloads, increasing latency and cost. ```typescript // BAD: unlimited text retrieval const bad = await exa.searchAndContents("topic", { numResults: 20, text: true, // could return megabytes of content }); // GOOD: limit content size const good = await exa.searchAndContents("topic", { numResults: 10, text: { maxCharacters: 2000 }, // cap at 2000 chars per result highlights: { maxCharacters: 500 }, }); ``` ## Pitfall 8: Creating New Client Per Request Each `new Exa()` call creates a new HTTP client. Reuse a singleton for connection pooling. ```typescript // BAD: new client every request (in a route handler) app.get("/search", async (req, res) => { const exa = new Exa(process.env.EXA_API_KEY); // wasteful! const results = await exa.search(req.query.q); res.json(results); }); // GOOD: singleton client const exa = new Exa(process.env.EXA_API_KEY); app.get("/search", async (req, res) => { const results = await exa.search(req.query.q); res.json(results); }); ``` ## Pitfall 9: Ignoring the requestId in Errors Exa error responses include `requestId` for support debugging. Always log it. ```typescript // BAD: generic error handling try { await exa.search("query"); } catch (err) { console.error("Search failed"); // loses diagnostic info } // GOOD: capture requestId try { await exa.search("query"); } catch (err: any) { console.error("Search failed:", { status: err.status, message: err.message, requestId: err.requestId, // include when contacting support tag: err.error_tag, }); } ``` ## Quick Review Checklist - [ ] Queries are natural language, not keyword/boolean syntax - [ ] Search type matches the query intent (neural vs keyword) - [ ] Using `searchAndContents` when page content is needed - [ ] Date filter windows are wide enough (7+ days) - [ ] `findSimilar` receives URLs, not query strings - [ ] No date filters on `company` or `people` categories - [ ] `maxCharacters` set on text and highlights - [ ] Exa client is a singleton, not created per request - [ ] Error handling captures `requestId` ## Resources - [Exa Search Reference](https://docs.exa.ai/reference/search) - [Exa Error Codes](https://docs.exa.ai/reference/error-codes) - [Exa Contents Retrieval](https://docs.exa.ai/reference/contents-retrieval) ## Next Steps For SDK patterns, see `exa-sdk-patterns`. For common errors, see `exa-common-errors`.