← All skills

Research · Web

Exa Search

Call Exa's semantic search over raw HTTP — the full POST /search surface with cURL you can paste: search types, domain and category filters, freshness-controlled crawling, highlights, structured output and streaming.

Skill name
exa-search
Triggers on
Call Exa Search directly with cURL or raw HTTP. Use when an agent needs Exa semantic web retrieval from POST /search without an SDK, including ranked results, domain or category filters, freshness-aware result content, highlights or text extraction, structured output, or streaming search responses.
Read time
12 min · Markdown · free to use and edit
Download .md

Use it in your assistant

Claude Code — drop the file in your skills folder and it loads on the next session. Use ~/.claude/skills for every project, or .claude/skills inside a repo to keep it to that project.

mkdir -p ~/.claude/skills/exa-search
curl -L https://growsteady.io/skills/exa-search/download -o ~/.claude/skills/exa-search/SKILL.md

Claude apps (web and desktop) — Settings → Capabilities → Skills → add a skill. Upload the file as SKILL.md inside a folder named exa-search (zip the folder if an archive is asked for).

No install— paste the file into a Claude Project's custom instructions with “Copy as prompt”. Same behaviour, scoped to that project.

Requires API key: Get one at https://dashboard.exa.ai/api-keys Header: x-api-key: $EXA_API_KEY

Use POST https://api.exa.ai/search for semantic web retrieval, ranked results, and optional result-level extraction in one raw HTTP call. Start with type: "auto" for general retrieval. Add contents only when the caller needs page text, highlights, summaries, freshness-controlled crawling, subpages, or extracted links.

Onboarding — start here

1. Is this the right Exa skill?

Three Exa skills exist in this workspace. Pick by what you already have:

You haveYou wantSkill
A question, no URLsFind the pages`exa-search` (this one)
URLs alreadyRead those pagesexa-contents
EitherBuild a product/agent on Exa, with SDKsbuild-with-exa

1b. Exa or Firecrawl? — the routing rule

Decide this before you call anything. The split is about how well-specified the target is:

SituationToolWhy
You do not know where the answer livesExa (this skill)Semantic discovery. Finds the source before anything can read it.
"Who else does X?", obscure topics, look-alike companiesExaFirecrawl returns the obvious pages; Exa finds the non-obvious ones.
You know the URLFirecrawl /scrapePrecise and cheap. No search needed at all.
You know the domain, want many pagesFirecrawl crawl / mapEnumerates a site you can already name.
Target is keyword-shaped and well-specifiedFirecrawl /searchCheaper for a target you can describe literally.

Said plainly: Firecrawl is superior once you know the website. Exa excels at finding hard-to-find websites.

The two compose, and that is usually the best pipeline: Exa to find the sources, Firecrawl to read them thoroughly. If you can already name the site, skip Exa — you are paying for discovery you don't need. When delegating to subagents, name the tool in the prompt so they follow the same split.

See firecrawl-build-search for the other half of this rule.

What this skill does not do. It covers one endpoint, POST /search, called over raw HTTP. It is not an SDK guide (exa-py / exa-jsbuild-with-exa), not a crawler (a whole site → Firecrawl crawl/map), not a scraper of pages you already have (→ exa-contents), and not a lead-list builder (→ lead-generation, which wraps Exa Agent and emits CSV). It also does not cover Websets, monitors, or the Exa Agent API.

2. Get the key set up (one time)

open https://dashboard.exa.ai/api-keys

Put it in local.env at the repo root (gitignored — never paste a key into a script or into this skill):

echo 'EXA_API_KEY=your_key_here' >> local.env

Load it into the shell before any call:

set -a && . ./local.env && set +a

3. Verify it works

This should return JSON with a results array. If it returns 401, the key is wrong or not loaded; if $EXA_API_KEY is empty, step 2 did not take.

curl -sS -X POST "https://api.exa.ai/search" -H "Content-Type: application/json" -H "x-api-key: $EXA_API_KEY" -d '{"query":"exa ai semantic search","type":"fast","numResults":3}'

4. Invoking the skill

Just describe the retrieval task — the skill triggers on intent, no slash command needed. Phrases that route here:

  • "search the web for companies doing X"
  • "find recent news about Y, last 30 days, Reuters and Bloomberg only"
  • "who else is writing about Z"
  • "call Exa /search directly with curl"

Say "with highlights" when you want the relevant excerpts back in the same call, and "deep" when the question needs multi-source synthesis rather than a list of links.

5. Read next

Go to Quick Start for copy-paste calls, Search Types to pick type, and Critical Pitfalls before writing your first non-trivial request — most first-time failures are the contents nesting rule listed there.

6. Cost awareness

Exa bills per request, and deep / deep-reasoning cost materially more than fast or auto. Before running a search in a loop over many rows, estimate the call count and tell the user what the run will cost. Check costDollars.total on the response to calibrate.

Quick Start (cURL)

Basic search

curl -sS -X POST "https://api.exa.ai/search" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $EXA_API_KEY" \
  -d '{
    "query": "latest developments in LLMs",
    "type": "auto",
    "numResults": 10
  }'

Search with highlights

curl -sS -X POST "https://api.exa.ai/search" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $EXA_API_KEY" \
  -d '{
    "query": "latest developments in LLMs",
    "type": "auto",
    "numResults": 5,
    "contents": {
      "highlights": true
    }
  }'

With filters and freshness

curl -sS -X POST "https://api.exa.ai/search" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $EXA_API_KEY" \
  -d '{
    "query": "AI regulation policy updates",
    "type": "auto",
    "category": "news",
    "numResults": 10,
    "includeDomains": ["reuters.com", "bbc.com"],
    "startPublishedDate": "2025-01-01",
    "contents": {
      "text": {
        "maxCharacters": 2000
      },
      "maxAgeHours": 24,
      "livecrawlTimeout": 12000
    }
  }'

Deep search

curl -sS -X POST "https://api.exa.ai/search" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $EXA_API_KEY" \
  -d '{
    "query": "map the major technical and commercial tradeoffs in sodium-ion batteries for grid storage",
    "type": "deep",
    "numResults": 8
  }'

Endpoint

POST https://api.exa.ai/search

Authentication: x-api-key: <API_KEY> header. Exa also accepts Authorization: Bearer <API_KEY>, but prefer x-api-key in cURL examples for consistency.

Use this endpoint when the agent needs search results. If the agent already has URLs and only needs extraction, use POST /contents instead.

Parameters

Core request parameters

ParameterTypeRequiredDefaultDescription
querystringYes-Natural-language search query. Long, semantically rich descriptions work well.
typestringNoautoSearch method: auto, fast, instant, deep-lite, deep, or deep-reasoning.
numResultsintegerNo10Number of results to return. Use small values for agent loops; maximum is 100.
categorystringNo-Specialized result type: company, people, research paper, news, personal site, or financial report.
includeDomainsstring[]No-Only return results from these domains, paths, or wildcard patterns. Max 1200.
excludeDomainsstring[]No-Exclude these domains, paths, or wildcard patterns. Max 1200.
startPublishedDatestringNo-ISO 8601 lower bound for result publication date.
endPublishedDatestringNo-ISO 8601 upper bound for result publication date.
userLocationstringNo-Two-letter ISO country code such as US or GB.
moderationbooleanNofalseFilter unsafe content from results.
additionalQueriesstring[]No-Extra query variants for deep-search variants. Use alongside the main query.
systemPromptstringNo-Instructions for synthesized output and deep-search planning, such as source preferences.
outputSchemaobjectNo-JSON Schema controlling output.content. Adds synthesized output and grounding.
streambooleanNofalseIf true, returns SSE instead of a single JSON response.
compliancestringNo-Enterprise-only compliance mode, such as hipaa, when enabled for the account.

Content parameters nested under contents

On /search, text, highlights, and summary must be nested under contents.

ParameterTypeRequiredDefaultDescription
contents.textboolean or objectNo-Return full page text as markdown. Object form supports maxCharacters, includeHtmlTags, verbosity, includeSections, and excludeSections.
contents.highlightsboolean or objectNo-Return query-relevant excerpts. Prefer true for agent workflows unless a fixed character budget is required.
contents.summaryboolean or objectNo-Return per-result LLM summaries. Use sparingly because each result adds synthesis work.
contents.maxAgeHoursintegerNo-Freshness control. 0 always live crawls; -1 uses cache only; omit for default cache-first behavior with crawl fallback.
contents.livecrawlTimeoutintegerNo10000Timeout for live crawling in milliseconds. Use 10000 to 15000 for most freshness-sensitive calls.
contents.subpagesintegerNo0Number of linked subpages to crawl per result.
contents.subpageTargetstring or string[]No-Terms used to prioritize which subpages matter, such as ["api", "pricing"].
contents.extras.linksintegerNo0Number of links to extract from each result page.
contents.extras.imageLinksintegerNo0Number of image URLs to extract from each result page.

Text object options

ParameterTypeDefaultDescription
maxCharactersinteger-Character limit for returned text. Use this instead of tokensNum.
includeHtmlTagsbooleanfalsePreserve HTML tags in output.
verbositystringcompactcompact, standard, or full. Pair fresh section-aware extraction with contents.maxAgeHours: 0.
includeSectionsstring[]-Only include selected sections: header, navigation, banner, body, sidebar, footer, metadata.
excludeSectionsstring[]-Exclude selected sections from the same section list.

Highlights object options

Prefer contents.highlights: true for the highest-quality default. Only use object form when the agent needs a custom focus or budget.

ParameterTypeDefaultDescription
querystring-Custom query guiding which excerpts are returned.
maxCharactersinteger-Cap highlight characters per URL. Omit unless the caller has a strict budget.

Summary object options

ParameterTypeDefaultDescription
querystring-Custom query for the summary.
schemaobject-JSON Schema for structured per-result summaries.

Search Types

Search type controls the retrieval and synthesis mode. Pick the mode for the workflow, not just the output format. outputSchema can be used with any search type; use deeper modes when the search process itself needs more planning, synthesis, or reasoning.

TypeBest forTradeoff
autoGeneral default search and most new integrationsBalances speed and quality without requiring the caller to tune retrieval strategy.
fastLow-latency agent loops and product pathsFaster than auto; use when responsiveness matters more than maximum reasoning depth.
instantReal-time UI, chat, voice, and autocomplete-style pathsLowest latency path; use for quick retrieval rather than deep synthesis.
deep-liteLightweight research or synthesisAdds more planning and synthesis than auto while staying lighter than full deep.
deepMulti-step research, comparisons, and synthesis-heavy retrievalHigher latency; better when the query needs exploration across several sources.
deep-reasoningHard research tasks with high ambiguity or complex tradeoffsHighest latency and reasoning depth.

Use auto unless latency or reasoning depth is the primary constraint. Use fast or instant for time-sensitive calls. Use deep, deep-lite, or deep-reasoning when the query needs multi-step source discovery, comparison, or synthesis.

Mode-only examples

{
  "query": "recent product launches from major AI chip companies",
  "type": "fast",
  "numResults": 5
}
{
  "query": "compare competing explanations for the recent rise in grid-scale battery deployments",
  "type": "deep",
  "numResults": 8
}

Structured Output

Use systemPrompt for behavior and outputSchema for shape.

curl -sS -X POST "https://api.exa.ai/search" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $EXA_API_KEY" \
  -d '{
    "query": "compare the latest frontier AI model releases",
    "type": "deep",
    "systemPrompt": "Prefer official sources and avoid duplicate results.",
    "outputSchema": {
      "type": "object",
      "properties": {
        "models": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "notable_claims": {
                "type": "array",
                "items": { "type": "string" }
              }
            },
            "required": ["name", "notable_claims"]
          }
        }
      },
      "required": ["models"]
    },
    "contents": {
      "highlights": true
    }
  }'

Keep schemas compact and bounded. Do not add citation fields to the schema; grounding is returned separately in output.grounding.

Streaming

Streaming applies to synthesized output, so include outputSchema along with -N, Accept: text/event-stream, and stream: true. Without outputSchema, the endpoint returns the normal JSON search response even when stream is true.

curl -sS -N -X POST "https://api.exa.ai/search" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -H "x-api-key: $EXA_API_KEY" \
  -d '{
    "query": "recent grid-scale battery deployments",
    "type": "deep",
    "stream": true,
    "outputSchema": {
      "type": "object",
      "properties": {
        "summary": { "type": "string" }
      },
      "required": ["summary"]
    },
    "contents": {
      "highlights": true
    }
  }'

Treat streaming as SSE rather than JSON. Each data: frame contains an OpenAI-compatible chat completion chunk; read partial text from choices[0].delta.content and handle completion or error frames defensively.

Response Fields

FieldTypeDescription
requestIdstringUnique request identifier.
resultsarrayRanked result objects.
results[].titlestringPage title.
results[].urlstringPage URL.
results[].publishedDatestring or nullEstimated publication date when available.
results[].authorstring or nullAuthor when available.
results[].textstringReturned when contents.text is requested.
results[].highlightsstring[]Returned when contents.highlights is requested.
results[].highlightScoresnumber[]Similarity scores for highlights.
results[].summarystringReturned when contents.summary is requested.
results[].subpagesarrayNested result objects from subpage crawling.
results[].extras.linksstring[]Extracted links when requested.
output.contentstring or objectSynthesized output when outputSchema is provided.
output.groundingarrayCitations and confidence labels for synthesized fields.
costDollars.totalnumberTotal request cost when returned.
searchTimenumberSearch latency when returned.

Critical Pitfalls

  • Keep text, highlights, and summary inside contents on /search.
  • Do not send top-level text, highlights, or summary; that shape belongs to /contents.
  • Do not send tokensNum; use contents.text.maxCharacters to cap extracted text.
  • Do not use useAutoprompt, numSentences, or highlightsPerUrl in new requests.
  • Use contents.maxAgeHours instead of livecrawl.
  • Use documented categories only: company, people, research paper, news, personal site, and financial report.
  • Avoid invalid category/filter combinations. company and people do not support startPublishedDate or endPublishedDate. company supports excludeDomains; people does not, and people only accepts LinkedIn domains in includeDomains.
  • Pick one of contents.highlights, contents.text, or contents.summary by default. Stack modes only when the caller truly needs multiple views of each page.
  • Expect SSE only when stream: true is paired with outputSchema; otherwise /search returns its normal JSON response.