Exa Integration¶
KeyPool supports the current official exa-js and exa-py clients through one team-authenticated service URL.
Setup¶
npm install exa-js
pip install exa-py
=== "TypeScript"
```typescript
import Exa from "exa-js";
const exa = new Exa(
process.env.KEYPOOL_TOKEN,
`${process.env.KEYPOOL_BASE_URL}/v1/exa`,
);
```
=== "Python"
```python
import os
from exa_py import Exa
exa = Exa(
api_key=os.environ["KEYPOOL_TOKEN"],
base_url=f'{os.environ["KEYPOOL_BASE_URL"]}/v1/exa',
)
```
Use the KeyPool Team Token as the SDK API key. Do not send a personal Exa key through this endpoint.
Supported SDK surface¶
| Capability | Current SDK entry point | Notes |
|---|---|---|
| Search | search, streamSearch / stream_search |
Regular and deep search modes, inline contents, structured deep output |
| Contents | getContents / get_contents |
Text, highlights, summaries, context, and subpages |
| Answer | answer, streamAnswer / stream_answer |
Answer text, citations, and streaming |
| Agent | agent.runs |
Create, stream, get, list, cancel, stop, delete, events, polling, and continuation. Stop is available for ultra runs. |
| Exa Connect | Agent dataSources / data_sources |
Premium data providers attached to an Agent run |
| Search Monitors | monitors, monitors.runs |
Monitor lifecycle, manual trigger, and run history |
| Research | research |
Legacy compatibility API |
Search types currently include keyword, neural, hybrid, auto, fast, instant, deep-lite, deep, and deep-reasoning. Use the option names exposed by your installed SDK version.
Search with contents¶
Prefer search with contents; the older combined convenience method is deprecated.
=== "TypeScript"
```typescript
const result = await exa.search("latest agent evaluation research", {
type: "auto",
numResults: 3,
contents: {
text: { maxCharacters: 2_000 },
highlights: { numSentences: 2 },
},
});
for (const item of result.results) {
console.log(item.title, item.url, item.text);
}
```
=== "Python"
```python
result = exa.search(
"latest agent evaluation research",
type="auto",
num_results=3,
contents={
"text": {"max_characters": 2_000},
"highlights": {"num_sentences": 2},
},
)
for item in result.results:
print(item.title, item.url, item.text)
```
For streamed search, iterate await exa.streamSearch(...) in TypeScript or exa.stream_search(...) in Python.
Contents and Answer¶
=== "TypeScript"
```typescript
const contents = await exa.getContents(["https://example.com"], {
text: { maxCharacters: 2_000 },
});
const answer = await exa.answer("What changed in the latest Exa Agent API?", {
text: true,
});
console.log(answer.answer, answer.citations);
```
=== "Python"
```python
contents = exa.get_contents(
["https://example.com"],
text={"max_characters": 2_000},
)
answer = exa.answer(
"What changed in the latest Exa Agent API?",
text=True,
)
print(answer.answer, answer.citations)
```
Use streamAnswer or stream_answer when the answer should arrive incrementally.
Exa Agent¶
Use the stable agent namespace for new code. beta.agent remains a deprecated compatibility namespace.
=== "TypeScript"
```typescript
const run = await exa.agent.runs.create({
query: "Find three companies building AI observability tools.",
effort: "low",
outputSchema: {
type: "object",
properties: {
companies: {
type: "array",
maxItems: 3,
items: {
type: "object",
properties: {
name: { type: "string" },
website: { type: "string", format: "uri" },
},
required: ["name", "website"],
},
},
},
required: ["companies"],
},
stream: false,
});
const completed = await exa.agent.runs.pollUntilFinished(run.id);
console.log(completed.output?.structured);
```
=== "Python"
```python
run = exa.agent.runs.create(
query="Find three companies building AI observability tools.",
effort="low",
output_schema={
"type": "object",
"properties": {
"companies": {
"type": "array",
"maxItems": 3,
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"website": {"type": "string", "format": "uri"},
},
"required": ["name", "website"],
},
}
},
"required": ["companies"],
},
)
completed = exa.agent.runs.poll_until_finished(run.id)
print(completed.output.structured)
```
Agent creation can also stream server-sent events. Set stream: true or stream=True and iterate the returned events. Retain the ID from agent_run.created if you need later get, cancel, delete, or event-history calls.
ultra is Exa's highest-effort tier and prioritizes completeness over latency and cost. The auto and ultra efforts are metered: set budget: { maxCostDollars: 1 } in TypeScript or budget={"maxCostDollars": 1} in Python when you need an explicit per-run ceiling. Exa accepts limits from $1 to $100; without one, the upstream default cap is $5 for auto and $20 for ultra. For ultra, you can also set maxDurationSeconds in the budget to a soft time limit of 300 to 10,800 seconds.
Current released SDK types also accept the older max spelling, but Exa's current API specification lists ultra instead. Use ultra for new runs; SDK typing alone does not establish that Exa still accepts max.
To finish an ultra run early with results gathered so far, call await exa.agent.runs.stop(run.id) in TypeScript or exa.agent.runs.stop(run.id) in Python. Exa bills usage accrued before the stop. To end a run without an early result, use cancel instead. See the official Agent API specification for the current effort and budget contract.
Continue a run¶
Each continuation creates a new run ID:
const followUp = await exa.agent.runs.create({
query: "Limit the list to companies hiring in Toronto.",
previousRunId: completed.id,
effort: "low",
stream: false,
});
Always keep both the previous and newly returned IDs.
Exa Connect¶
Connect is part of Agent creation, not a separate SDK namespace. Provider identifiers use the API form shown in Exa's Connect documentation.
=== "TypeScript"
```typescript
const run = await exa.agent.runs.create({
query: "Return one public web-traffic fact about exa.ai.",
dataSources: [{ provider: "similarweb" }],
effort: "low",
stream: false,
});
```
=== "Python"
```python
run = exa.agent.runs.create(
query="Return one public web-traffic fact about exa.ai.",
data_sources=[{"provider": "similarweb"}],
effort="low",
)
```
Connect providers may require upstream entitlement and add provider-specific usage charges. Bound list sizes and structured schemas before running large jobs.
Search Monitors¶
Search Monitors require an HTTPS webhook URL when created:
const monitor = await exa.monitors.create({
name: "Exa release monitor",
search: { query: "Exa API release notes", numResults: 5 },
trigger: { type: "interval", period: "1d" },
webhook: { url: process.env.MONITOR_WEBHOOK_URL! },
});
await exa.monitors.trigger(monitor.id);
const runs = await exa.monitors.runs.list(monitor.id, { limit: 10 });
Use get, update, and delete with the returned monitor ID.
Unavailable: Websets¶
KeyPool does not expose the Websets API or its items, searches, enrichments, events, imports, monitors, and webhooks. Exa requires a paid plan for this product, and Websets are not accessible with free Exa credits.
The official SDKs may still display a websets namespace, but calls through the KeyPool Exa service are unsupported. Use Search, Agent, or Agent with Exa Connect instead.
Resource IDs and list calls¶
For reliable multi-step workflows:
- Create the resource through KeyPool.
- Persist the returned ID.
- Perform get, update, cancel, event, and delete calls through the same KeyPool base URL and Team Token.
List endpoints are useful for discovery but should not be treated as a complete KeyPool-wide inventory. Resource IDs returned by the same workflow are the dependable way to address stateful objects.
Legacy and deprecated APIs¶
- Prefer
search(..., contents=...)oversearchAndContents/search_and_contents. findSimilar,findSimilarAndContents, and Python equivalents remain available for compatibility but are deprecated.- Prefer
agentoverbeta.agentfor new code. beta.agent.runs.stop()remains a deprecated compatibility call; useagent.runs.stop()in current TypeScript and Python SDKs for early completion ofultraruns.researchremains supported for existing integrations; start new long-running structured research with Agent.
Errors¶
401: missing or invalid KeyPool Team Token.403: the token cannot access Exa, or an upstream capability requires additional entitlement.404 CAPABILITY_NOT_SUPPORTED: the requested path belongs to the paid-plan Websets family and is not exposed by KeyPool.429: KeyPool quota or upstream rate limit reached.503 NO_AVAILABLE_KEYS: no eligible Exa credential is available for a new request.503 AFFINITY_KEY_UNAVAILABLE: the upstream credential associated with a previously returned Exa resource is unavailable. Retry later rather than recreating the request against a different resource ID.
See API Reference → Exa for the SDK-backed HTTP surface.