Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Connect AI agents to Exa

Scalekit connector
Open markdown

The Exa connector lets your AI agent act in each user's Exa account. Each user connects their own Exa API key once, and Scalekit sends it with every call, so your agent never handles credentials. It comes with 21 tools.

Tools
21
What they doRead · write · destructive
12 · 8 · 112 read8 write1 destructive
Users sign in with

What you can do

  • Search the web: run semantic or keyword searches filtered by domain, date range, and content category
  • Get cited answers: answer questions with a synthesized response and citations to the source pages
  • Read web pages: crawl pages by URL to get their full text, highlights, and summaries
  • Find similar pages: discover pages, articles, and companies similar to a given URL
  • Run deep research: start research tasks that read many sources and return a single well-cited answer
  • Build and monitor websets: collect large sets of matching URLs, enrich each item, and refresh them on a schedule

Setup

  1. Install the SDK

    Terminal window
    npm install @scalekit-sdk/node dotenv
  2. Set your credentials

    Add your Scalekit credentials to your .env file. Find values in app.scalekit.com > Developers > API Credentials.

    .env
    SCALEKIT_ENVIRONMENT_URL=<your-environment-url>
    SCALEKIT_CLIENT_ID=<your-client-id>
    SCALEKIT_CLIENT_SECRET=<your-client-secret>
  3. Create the Exa connection

    In AgentKit > Connections, create an Exa connection. The name you give it is the connection_name your code passes. See Configure connections.

    Console steps with screenshots

    Register your Exa API key with Scalekit so it can authenticate and proxy requests on behalf of your users. Exa uses API key authentication.

    1. Generate an Exa API key

      • Sign in to dashboard.exa.ai/api-keys. Under Management, click API Keys.

      • Click + Create Key, enter a name (e.g., Agent Auth), and confirm.

      • In the Secret Key column, click the eye icon to reveal the key and copy it. Store it somewhere safe — you will not be able to view it again.

      Exa dashboard API Keys page showing existing keys and the + Create Key button

    2. Create a connection in Scalekit

      • In Scalekit dashboard, go to AgentKit > Connections > Create Connection. Find Exa and click Create.

      • Note the Connection name — you will use this as connection_name in your code (e.g., exa).

      Scalekit connection configuration for Exa showing the connection name and API Key authentication type

    3. Add a connected account

      Connected accounts link a specific user identifier in your system to an Exa API key. Add one in the dashboard to test. In production, each user adds their own through the authorization link: they enter their credentials on the page it opens.

      In the dashboard, to test

      • Open the connection you created and click the Connected Accounts tab → Add account.

      • Fill in:

        • Your User’s ID — a unique identifier for this user in your system (e.g., user_123)
        • API Key — the Exa API key you copied in step 1
      • Click Save.

      Add connected account form for Exa in Scalekit dashboard

      From your backend, if your app already has the credentials

      For example, when users enter them on a settings page in your app:

      import { ConnectorStatus } from '@scalekit-sdk/node'
      const authorizationDetails = {
      details: {
      case: 'staticAuth',
      value: { details: { api_key: 'your-exa-api-key' } },
      },
      };
      let { connectedAccount } = await scalekit.actions.upsertConnectedAccount({
      connectionName: 'exa',
      identifier: 'user_123',
      authorizationDetails,
      });
      // Make sure the account is ACTIVE before the first tool call.
      if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
      ({ connectedAccount } = await scalekit.actions.upsertConnectedAccount({
      connectionName: 'exa',
      identifier: 'user_123',
      authorizationDetails,
      }));
      }
  4. Authorize a user and make your first call

    quickstart.mts
    import { ScalekitClient } from '@scalekit-sdk/node'
    import 'dotenv/config'
    import { createInterface } from 'node:readline/promises'
    const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL,
    process.env.SCALEKIT_CLIENT_ID,
    process.env.SCALEKIT_CLIENT_SECRET,
    )
    const actions = scalekit.actions
    const connector = 'exa'
    const identifier = 'user_123'
    // Generate an authorization link for the user
    const { link } = await actions.getAuthorizationLink({ connectionName: connector, identifier })
    console.log('Authorize Exa:', link)
    const rl = createInterface({ input: process.stdin, output: process.stdout })
    await rl.question('Press Enter after authorizing...')
    rl.close()
    // Make your first call
    const result = await actions.executeTool({
    connector,
    identifier,
    toolName: 'exa_list_webset_monitors',
    toolInput: {},
    })
    console.log(result)
    Terminal window
    npx tsx quickstart.mts

    Each user opens the link once and enters their Exa credentials there. If your app already has a user's credentials, add the account from your backend instead, as the console steps above show. See Authorize a user for the full flow and statuses.

Tools

Pass the exact name to execute_tool
Try in PlaygroundRequest a tool
  • exa_answerGet a natural language answer to a question by searching the web with Exa and synthesizing results.Read-only

    Get Answer

    Get a natural language answer to a question by searching the web with Exa and synthesizing results. Returns a direct answer with citations to the source pages. Ideal for factual questions, current events, and research queries. Rate limit: 60 requests/minute.

    Inputs

    querystringrequired
    The question or query to answer from web sources.
    exclude_domainsarray
    JSON array of domains to exclude from answer sources.
    include_domainsarray
    JSON array of domains to restrict source search to. Example: ["reuters.com","bbc.com"]
    include_textboolean
    When true, also returns the source page text alongside the synthesized answer.
    num_resultsinteger
    Number of web sources to use when generating the answer (1–20). More sources improves accuracy but costs more credits.
  • exa_crawlCrawl one or more web pages by URL and extract their content including full text, highlights, and AI-generated summaries.Read-only

    Crawl Pages

    Crawl one or more web pages by URL and extract their content including full text, highlights, and AI-generated summaries. Useful for reading specific pages discovered via search. Rate limit: 60 requests/minute. Credit consumption depends on number of URLs.

    Inputs

    urlsarrayrequired
    JSON array of URLs to crawl and extract content from.
    highlights_per_urlinteger
    Number of highlight sentences to return per URL when include_highlights is true. Defaults to 3.
    include_highlightsboolean
    When true, returns the most relevant sentence-level highlights from each page.
    include_html_tagsboolean
    When true, retains HTML tags in the extracted text. Defaults to false (plain text only).
    include_summaryboolean
    When true, returns an AI-generated summary for each crawled page.
    max_charactersinteger
    Maximum characters of text to extract per page. Defaults to 5000.
    summary_querystring
    Optional query to focus the AI summary on a specific aspect of the page.
  • exa_find_similarFind web pages similar to a given URL using Exa's neural similarity search.Read-only

    Find Similar Pages

    Find web pages similar to a given URL using Exa's neural similarity search. Useful for competitor research, finding related articles, or discovering similar companies. Optionally returns page text, highlights, or summaries. Rate limit: 60 requests/minute.

    Inputs

    urlstringrequired
    The URL to find similar pages for.
    end_published_datestring
    Only return pages published before this date. ISO 8601 format: YYYY-MM-DDTHH:MM:SS.000Z
    exclude_domainsarray
    Array of domains to exclude from results.
    include_domainsarray
    Array of domains to restrict results to.
    include_textboolean
    When true, returns the full text content of each result page.
    max_charactersinteger
    Maximum characters of page text to return per result when include_text is true. Defaults to 3000.
    num_resultsinteger
    Number of similar results to return (1–100). Defaults to 10.
    start_published_datestring
    Only return pages published after this date. ISO 8601 format: YYYY-MM-DDTHH:MM:SS.000Z
  • exa_get_research_taskCheck the status of a Research Task and retrieve its output once complete.Read-only

    Get Research Task

    Check the status of a Research Task and retrieve its output once complete. Use the task ID returned by Create Research Task.

    Inputs

    task_idstringrequired
    The ID of the research task to retrieve.
  • exa_get_websetGet the status and details of an existing Exa Webset by its ID.Read-only

    Get Webset

    Get the status and details of an existing Exa Webset by its ID. Use this to poll the status of an async webset created with Create Webset. Returns metadata including status (created, running, completed, cancelled), progress, and configuration.

    Inputs

    webset_idstringrequired
    The ID of the webset to retrieve.
  • exa_get_webset_itemRetrieve a single item from an Exa Webset by its item ID, including its full enrichment data and verification evidence.Read-only

    Get Webset Item

    Retrieve a single item from an Exa Webset by its item ID, including its full enrichment data and verification evidence. Use List Webset Items to find item IDs.

    Inputs

    item_idstringrequired
    The ID of the item to retrieve.
    webset_idstringrequired
    The ID of the webset that contains the item.
  • exa_get_webset_monitorGet a single Monitor's configuration, enabled/disabled status, cadence, and last/next run details.Read-only

    Get Webset Monitor

    Get a single Monitor's configuration, enabled/disabled status, cadence, and last/next run details.

    Inputs

    monitor_idstringrequired
    The ID of the monitor to retrieve.
  • exa_list_webset_itemsList the collected URLs and items from a completed Exa Webset.Read-only

    List Webset Items

    List the collected URLs and items from a completed Exa Webset. Use this after polling Get Webset until its status is 'completed' to retrieve the discovered results.

    Inputs

    webset_idstringrequired
    The ID of the webset to retrieve items from.
    countinteger
    Number of items to return per page. Defaults to 10.
    cursorstring
    Pagination cursor from a previous response to fetch the next page of items.

Workflows

Proxy API call
const result = await actions.request({
connectionName: 'exa',
identifier: 'user_123',
path: '/search',
method: 'POST',
body: { query: 'LLM observability tools 2025', num_results: 5 },
});
console.log(result.data);
Semantic search

Search the web by meaning, not just keywords. This example searches for companies in the AI infrastructure space and returns AI-generated summaries for each result.

result = actions.execute_tool(
connection_name='exa',
identifier='user_123',
tool_name="exa_search",
tool_input={
"query": "AI infrastructure companies building GPU cloud platforms",
"num_results": 10,
"type": "neural",
"category": "company",
"contents": {
"summary": {"query": "What does this company do and who are their customers?"}
}
}
)
for item in result.data.get("results", []):
print(f"{item['title']}: {item['url']}")
print(f" → {item.get('summary', 'No summary')}\n")
Search with full content enrichment

Retrieve the full page text and highlighted snippets alongside search results — useful when you want to pass source material directly into an LLM context window.

result = actions.execute_tool(
connection_name='exa',
identifier='user_123',
tool_name="exa_search",
tool_input={
"query": "OpenAI API rate limits and pricing 2025",
"num_results": 5,
"type": "keyword", # keyword mode for precise terms
"include_domains": ["openai.com", "platform.openai.com"],
"contents": {
"text": {"max_characters": 2000}, # cap text to save tokens
"highlights": {
"num_sentences": 3,
"highlights_per_url": 2
}
}
}
)
for item in result.data.get("results", []):
print(f"## {item['title']}")
print(f"URL: {item['url']}")
if item.get("highlights"):
print("Highlights:")
for h in item["highlights"]:
print(f" - {h}")
print()
Find similar pages

Discover pages that are semantically similar to a known URL — useful for competitive research, finding alternative data sources, or discovering similar products.

# Find companies similar to a known competitor
result = actions.execute_tool(
connection_name='exa',
identifier='user_123',
tool_name="exa_find_similar",
tool_input={
"url": "https://www.linear.app",
"num_results": 10,
"exclude_domains": ["linear.app"], # exclude the source URL itself
"start_published_date": "2024-01-01", # only recently indexed pages
"contents": {
"summary": {"query": "What product does this company build?"}
}
}
)
print("Similar companies to Linear:")
for item in result.data.get("results", []):
print(f" {item['title']} — {item['url']}")
if item.get("summary"):
print(f" {item['summary']}")
Get content for known URLs

Extract structured content from a list of URLs you already have — from a CRM export, a prior search, or a manually curated list. No search query required.

# Enrich a list of company URLs from your CRM
company_urls = [
"https://www.anthropic.com",
"https://mistral.ai",
"https://cohere.com",
]
result = actions.execute_tool(
connection_name='exa',
identifier='user_123',
tool_name="exa_crawl",
tool_input={
"urls": company_urls,
"include_summary": True,
"summary_query": "What AI models or products does this company offer, and who are their target customers?",
"max_characters": 5000 # cap extracted text per page
}
)
for item in result.data.get("results", []):
print(f"{item['url']}: {item.get('summary', 'No summary')}")
Get a direct answer

Ask a question and get a synthesized natural language answer grounded in live web sources. Returns the answer and the source URLs used — ready to display or inject into a citation-aware LLM prompt.

result = actions.execute_tool(
connection_name='exa',
identifier='user_123',
tool_name="exa_answer",
tool_input={
"query": "What are the context window sizes and pricing for Claude Sonnet and GPT-4o as of 2025?",
"num_results": 8,
"text": True, # include source snippets
"include_domains": ["anthropic.com", "openai.com", "platform.openai.com"]
}
)
print("Answer:", result.data.get("answer"))
print("\nSources:")
for source in result.data.get("sources", []):
print(f" - {source['title']}: {source['url']}")
Deep research on a topic

Run multi-angle research that decomposes your topic into parallel sub-queries and synthesizes the results. Use output_schema to get structured JSON instead of free-form text — useful for generating reports your code can consume directly.

result = actions.execute_tool(
connection_name='exa',
identifier='user_123',
tool_name="exa_research",
tool_input={
"topic": "Competitive landscape of AI coding assistants in 2025 — key players, pricing, and differentiators",
"num_subqueries": 5,
"output_schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"competitors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"pricing": {"type": "string"},
"key_differentiator": {"type": "string"},
"target_customer": {"type": "string"}
}
}
},
"market_trends": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["summary", "competitors", "market_trends"]
}
}
)
report = result.data
print("Summary:", report.get("summary"))
print("\nCompetitors:")
for c in report.get("competitors", []):
print(f" {c['name']}: {c.get('key_differentiator')}")
print("\nTrends:")
for t in report.get("market_trends", []):
print(f" - {t}")
LangChain integration

Let an LLM decide which Exa tool to call based on natural language. This example builds an agent that can search, retrieve content, and answer research questions on demand.

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import (
ChatPromptTemplate, SystemMessagePromptTemplate,
HumanMessagePromptTemplate, MessagesPlaceholder, PromptTemplate
)
# Load all Exa tools in LangChain format. Use page_size=100 so connector tool lists are not truncated.
tools = actions.langchain.get_tools(
identifier='user_123',
providers=["EXA"],
page_size=100
)
prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate(prompt=PromptTemplate(
input_variables=[],
template=(
"You are a research assistant with access to Exa web search tools. "
"Use exa_search for general queries, exa_answer for direct questions, "
"exa_find_similar for competitive analysis, and exa_research for deep multi-source topics. "
"Always cite your sources."
)
)),
MessagesPlaceholder(variable_name="chat_history", optional=True),
HumanMessagePromptTemplate(prompt=PromptTemplate(
input_variables=["input"], template="{input}"
)),
MessagesPlaceholder(variable_name="agent_scratchpad")
])
llm = ChatOpenAI(model="gpt-4o")
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = agent_executor.invoke({
"input": "Who are the top 5 competitors to Notion for team knowledge management? Summarize each and compare their pricing."
})
print(result["output"])