The official JavaScript SDK for Exa, the web search API built for AI.
npm install exa-jsimport 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?");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
Deep search variants that also support additionalQueries:
deep-litedeepdeep-reasoning
Get clean text, highlights, or summaries from any URL.
const { results } = await exa.getContents(["https://docs.exa.ai"], {
text: true,
highlights: true,
summary: true,
});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);
}
}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" }),
],
});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 Max, use the beta namespace and pass the beta token explicitly:
import { AGENT_MAX_EFFORT_BETA } from "exa-js";
const maxRun = await exa.beta.agent.runs.create({
query:
"Find all companies building browser automation tools in the United States.",
effort: "max",
budget: { maxCostDollars: 10 },
betas: [AGENT_MAX_EFFORT_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 stateless snapshot of a past news window — no monitor created.
const snapshot = await exa.beta.agent.monitors.snapshots.createAndWait({
betas,
entities: [{ name: "Acme Corp", domain: "acme.com" }],
fields: [
{ name: "funding", description: "New funding rounds" }, // dynamic by default
],
startDate: "2026-01-01",
endDate: "2026-01-08",
});
console.log(snapshot.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 });Full TypeScript support with types for all methods.
import Exa from "exa-js";
import type { SearchResponse, RegularSearchOptions } from "exa-js";Pull requests welcome! For major changes, open an issue first.
MIT.