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, delete, events, polling, and continuation |
| 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. beta.agent remains a deprecated alias.
=== "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.
max is Exa's highest-effort public beta tier and prioritizes completeness over latency and cost. The auto and max 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 max.
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.agent. 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.