exa-js
v2.22.2
Published
Exa SDK for Node.js and the browser
Downloads
3,264,462
Readme
Exa JavaScript SDK
The official JavaScript SDK for Exa, the web search API built for AI.
Install
npm install exa-jsQuick 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
Deep search variants that also support additionalQueries:
deep-litedeepdeep-reasoning
Contents
Get clean text, highlights, or summaries from any URL.
const { results } = await exa.getContents(["https://docs.exa.ai"], {
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 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 (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.
