Exa JavaScript SDK
The official JavaScript SDK for Exa, the web search API built for AI.
Install
npm install exa-js
Quick Start
import Exa from "exa-js";
const exa = new Exa(process.env.EXA_API_KEY);
// Search the web
const result = await exa.search("blog post about artificial intelligence", {
type: "auto",
contents: {
highlights: true,
},
});
// Get answers with citations
const { answer } = await exa.answer("What is the capital of France?");
Search
Find webpages using natural language queries.
const result = await exa.search("interesting articles about space", {
numResults: 10,
includeDomains: ["nasa.gov", "space.com"],
startPublishedDate: "2024-01-01",
contents: {
highlights: true,
},
});
const resultWithOutput = await exa.search("Who leads OpenAI's safety team?", {
type: "auto",
systemPrompt: "Prefer official sources and avoid duplicate results",
outputSchema: {
type: "object",
properties: {
leader: { type: "string" },
title: { type: "string" },
sourceCount: { type: "number" },
},
required: ["leader", "title"],
},
});
console.log(resultWithOutput.output?.content);
for await (const chunk of exa.streamSearch("Who leads OpenAI's safety team?", {
type: "auto",
})) {
if (chunk.content) {
process.stdout.write(chunk.content);
}
}
Search outputSchema modes:
type: "text": return plain text inoutput.content(optionally guided bydescription)type: "object": return structured JSON inoutput.content
systemPrompt and outputSchema are supported on every search type.
Search streaming is available via streamSearch(...), which yields OpenAI-style chat completion chunks.
For type: "object", search currently enforces:
- max nesting depth:
2 - max total properties:
10
additionalQueries:
deep-litedeepdeep-reasoning
Raw body fields
Pass requestOptions.body as the third argument for per-call search body overrides.
const result = await exa.search("AI research", undefined, { body: { futureField: true } });
Contents
Get clean text, highlights, or summaries from any URL.
const { results } = await exa.getContents(["https://exa.ai/docs"], {
text: true,
highlights: true,
summary: true,
});
Answer
const response = await exa.answer("What caused the 2008 financial crisis?");
console.log(response.answer);
for await (const chunk of exa.streamAnswer("Explain quantum computing")) {
if (chunk.content) {
process.stdout.write(chunk.content);
}
}
Web Search and Contents tools
Use Exa as a web_search tool in an OpenAI or Anthropic loop. Call webSearch() with no arguments to get Exa's recommended settings for agentic search (type: "auto" and contents: { highlights: true }).
import Exa from "exa-js";
import { OpenAI } from "openai";
const exa = new Exa(process.env.EXA_API_KEY);
const openai = new OpenAI();
const messages = [{ role: "user", content: "What's the latest on AI chips?" }];
const completion = await openai.chat.completions.create({
model: "gpt-5.6",
messages,
tools: [exa.openai.webSearch()],
});
const message = completion.choices[0].message;
messages.push(message, ...(await exa.openai.handleToolCalls(message)));
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages,
tools: [exa.anthropic.webSearch()],
});
For the OpenAI Responses API, use exa.openai.responses.webSearch() and the same handleToolCalls helper.
Pass name (and optionally description) to rename the tool — for example to run Exa alongside Anthropic's built-in web_search_20250305 server tool, which reserves the web_search name:
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages,
tools: [
{ type: "web_search_20250305", name: "web_search", max_uses: 5 },
exa.anthropic.webSearch({ name: "exa_web_search" }),
],
});
getContents() is available in the same namespaces and lets the model read pages it already has URLs for. It takes a list of URLs and accepts every exa.getContents option:
const completion = await openai.chat.completions.create({
model: "gpt-5.6",
messages,
tools: [
exa.openai.webSearch(),
exa.openai.getContents({ summary: true, livecrawl: "preferred" }),
],
});
Agent API
const run = await exa.agent.runs.create({
query:
"Find engineering leaders at AI infrastructure companies that raised a Series A or B in the last 6 months.",
outputSchema: {
type: "object",
properties: {
people: {
type: "array",
maxItems: 10,
items: {
type: "object",
properties: {
name: { type: "string" },
contact_email: { type: "string", format: "email" },
linkedin_url: { type: "string", format: "uri" },
},
required: ["name", "linkedin_url"],
},
},
},
required: ["people"],
},
effort: "auto",
// Optionally enable Exa Connect data providers for the run.
dataSources: [{ provider: "financial_datasets" }],
});
const completedRun = await exa.agent.runs.pollUntilFinished(run.id);
console.log(completedRun.output?.structured);
// Per-provider tool-call counts and cost for any Exa Connect data sources used.
console.log(
completedRun.usage?.dataSources,
completedRun.costDollars?.dataSources
);
For Agent Ultra, set effort: "ultra":
const ultraRun = await exa.agent.runs.create({
query:
"Find all companies building browser automation tools in the United States.",
effort: "ultra",
budget: { maxCostDollars: 10 },
});
Agent Monitors (Beta)
Agent Monitors use the beta namespace and require the AGENT_MONITORS_BETA_HEADER beta identifier (agent-monitors-2026-08-04).
An Agent Monitor keeps a table of entities × fields fresh on a cadence: static fields are answered once per entity over the live web, dynamic fields are tracked from news on every refresh.
import { AGENT_MONITORS_BETA_HEADER } from "exa-js";
const betas = [AGENT_MONITORS_BETA_HEADER];
// Create a monitor. Creation is async: it returns with status "creating"
// and becomes "active" once the first refresh completes.
const monitor = await exa.beta.agent.monitors.create(
{
betas,
cadence: "7d",
entities: [
{ name: "Acme Corp", domain: "acme.com" },
{ name: "Globex", domain: "globex.com" },
],
fields: [
{ name: "funding", description: "New funding rounds" }, // dynamic by default
{ name: "ceo", description: "The company's current CEO", mode: "static" },
],
},
{ idempotencyKey: "my-monitor-1" } // safe retries: same key returns the same monitor
);
// Page the monitor's current entities and their contents.
for await (const {
entity,
contents,
} of exa.beta.agent.monitors.entities.listAll(monitor.id, { betas })) {
console.log(entity.name, contents);
}
// Follow the content change feed (resume later from the page's nextCursor).
const changes = await exa.beta.agent.monitors.changes.list(monitor.id, {
betas,
since: "2026-01-01T00:00:00Z",
});
// One-shot backtest of a past news window — no monitor persists.
const backtest = await exa.beta.agent.monitors.backtests.createAndWait({
betas,
entities: [{ name: "Acme Corp", domain: "acme.com" }],
fields: [{ name: "funding", description: "New funding rounds" }],
startTime: "2026-01-01T00:00:00Z",
endTime: "2026-01-08T00:00:00Z",
});
console.log(backtest.data);
// Add entities, inspect refresh progress, clean up.
await exa.beta.agent.monitors.entities.add(monitor.id, {
betas,
entities: [{ name: "Initech", domain: "initech.com" }],
});
const current = await exa.beta.agent.monitors.get(monitor.id, { betas });
console.log(current.status, current.refresh, current.usage);
await exa.beta.agent.monitors.delete(monitor.id, { betas });
TypeScript
Full TypeScript support with types for all methods.
import Exa from "exa-js";
import type { SearchResponse, RegularSearchOptions } from "exa-js";
Links
Contributing
Pull requests welcome! For major changes, open an issue first.
License
MIT.