# Agent API Reference

Async multi-step research, list-building, enrichment, and structured extraction via `POST /agent/runs`.

## Canonical Docs Links

- Agent API guide: `https://exa.ai/docs/agent/quickstart`
- Create a run: `https://exa.ai/docs/reference/agent-api/create-a-run`
- Get a run: `https://exa.ai/docs/reference/agent-api/get-a-run`
- List runs: `https://exa.ai/docs/reference/agent-api/list-runs`
- List run events: `https://exa.ai/docs/reference/agent-api/list-run-events`
- Cancel a run: `https://exa.ai/docs/reference/agent-api/cancel-a-run`
- Delete a run: `https://exa.ai/docs/reference/agent-api/delete-a-run`
- Exa Connect overview: `https://exa.ai/docs/agent/connect/quickstart`
- Connect combining providers: `https://exa.ai/docs/agent/connect/combining-providers`
- Connect providers: `https://exa.ai/docs/agent/connect/fiber`, `https://exa.ai/docs/agent/connect/similarweb`, `https://exa.ai/docs/agent/connect/baselayer`, `https://exa.ai/docs/agent/connect/affiliatecom`, `https://exa.ai/docs/agent/connect/particle`, `https://exa.ai/docs/agent/connect/financialdatasets`, `https://exa.ai/docs/agent/connect/jinko`, `https://exa.ai/docs/agent/connect/additional-partners`

## Overview

Use `/agent` when a workflow needs more than a single search or extraction call:

- build lists from open-ended criteria, then enrich each result
- research entities across many fields with citations
- run multi-hop tasks such as "find companies, then find decision makers"
- produce structured JSON from a long-running web research task
- continue from a previous run with a follow-up request

For simpler low-latency retrieval, prefer `/search`.

## Request Shape

```json
POST https://api.exa.ai/agent/runs
{
  "query": "Find engineering leaders at AI infrastructure companies that raised a Series A or B in the last 6 months.",
  "effort": "auto",
  "outputSchema": {
    "type": "object",
    "properties": {
      "people": {
        "type": "array",
        "maxItems": 10,
        "items": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "job_title": { "type": "string" },
            "linkedin_url": { "type": "string", "format": "uri" }
          },
          "required": ["name", "job_title", "linkedin_url"]
        }
      }
    },
    "required": ["people"]
  }
}
```

## Core Fields

| Field | Type | Notes |
| --- | --- | --- |
| `query` | string | Required natural-language task |
| `input.data` | object[] | Existing rows to research or enrich |
| `input.exclusion` | object[] | Records or entities Agent should avoid surfacing |
| `outputSchema` | object | JSON Schema for validated `output.structured` |
| `previousRunId` | string | Continue from a completed prior run |
| `effort` | string | Always set explicitly: `minimal`, `low`, `medium`, `high`, `xhigh`, `auto`, or `ultra`. |
| `budget.maxCostDollars` | number | Per-run spend ceiling in dollars, `1` to `100`. Only accepted with `auto` and `ultra`; defaults to `$5` for `auto` and `$20` for `ultra`. |
| `budget.maxDurationSeconds` | number | Soft wall-clock ceiling in seconds, `300` to `10800`. Only accepted with `ultra`; the run returns what it has with `stopReason: "time_limit_reached"`. |
| `dataSources` | object[] | Exa Connect providers to attach to the run, for example `{ "provider": "similarweb" }` |

`outputSchema` supports JSON Schema. Bound list outputs with `maxItems` where possible so output size and enrichment cost are predictable.

Always send an explicit `effort`. Prefer `auto` unless the task or product needs a fixed cost/latency band (`low` for cheap/fast, `high` / `xhigh` for harder research). Use `ultra` for large list building, exhaustive multi-source research, or hard-to-verify criteria where completeness matters more than latency or cost.

To request contact information, describe the desired contact fields in the schema. Use standard JSON Schema formats such as `{ "type": "string", "format": "email" }`, `{ "type": "string", "format": "phone" }`, and `{ "type": "string", "format": "uri" }`.

`auto` and `ultra` are metered by usage and capped by `budget.maxCostDollars` (default `$5` for `auto`, `$20` for `ultra`). The cap is a ceiling, not a fixed price: runs that finish early cost less. Fixed efforts bill a flat per-request price and reject `budget`.

## Lifecycle

Create returns a run object immediately; final output is available only after a terminal status. Every integration must complete this cycle; do not stop at create:

1. Create a run with `POST /agent/runs`.
2. Save the returned `id`, which has the `agent_run_` prefix.
3. Wait for the run to reach a terminal status (`completed`, `failed`, or `cancelled`), either by:
   - **Polling:** `GET /agent/runs/{id}` until `status` is terminal, or
   - **SSE events:** `Accept: text/event-stream` on create, or replay from `GET /agent/runs/{id}/events` with `Last-Event-ID`.
   The two are equivalent; pick one per integration.
4. Check how the run ended: on `completed`, read `output`; on `failed` or `cancelled`, surface the error to the caller or UI. Only `completed` carries results — reading `output` without checking the status makes failures look like empty successes, and a hand-rolled wait loop that exits only on `completed` never finishes for failed runs.

Completed runs include:

- `output.text`: natural-language answer or summary
- `output.structured`: validated JSON matching `outputSchema`, when provided
- `output.grounding`: citations for text or structured fields
- `costDollars`: run cost breakdown
- `stopReason`: `schema_satisfied`, `budget_reached`, `time_limit_reached` (`ultra` only), `stopped`, `error`, or `cancelled`

## Polling

```bash
RUN_ID="agent_run_01j..."

while true; do
  RUN_JSON="$(curl -s "https://api.exa.ai/agent/runs/$RUN_ID" \
    -H "x-api-key: $EXA_API_KEY")"

  STATUS="$(echo "$RUN_JSON" | jq -r '.status')"
  echo "status=$STATUS"

  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "cancelled" ]; then
    echo "$RUN_JSON" | jq .
    break
  fi

  sleep 4
done
```

SDK helpers are available:

- Python: `exa.agent.runs.poll_until_finished(run_id, poll_interval=4000)`
- TypeScript: `exa.agent.runs.pollUntilFinished(runId, { pollInterval: 4000 })`

## Streaming and Events

Set `Accept: text/event-stream` when creating a run to receive lifecycle events until a terminal status.

For stored event replay:

```bash
curl -N "https://api.exa.ai/agent/runs/agent_run_01j.../events" \
  -H "Accept: text/event-stream" \
  -H "Last-Event-ID: 1" \
  -H "x-api-key: $EXA_API_KEY"
```

Without SSE, `GET /agent/runs/{id}/events` returns paginated JSON. Use `cursor` for JSON pagination and `Last-Event-ID` for SSE replay.

## Follow-up Runs

Use `previousRunId` to ask follow-up questions over a completed prior run:

```json
{
  "query": "Narrow that list to companies hiring in San Francisco.",
  "previousRunId": "agent_run_01j..."
}
```

The previous run must be completed and belong to the same team. A follow-up is a new create request that returns a new run ID (`agent_run_*`); `previousRunId` supplies prior-run context, it does not reuse the prior run's ID or object.

## Connect Data Sources

Use `dataSources` to attach Exa Connect providers to a run. The agent can call those partner tools alongside Exa web search when the `query` and `outputSchema` explicitly ask for the partner-specific data.

```json
{
  "dataSources": [
    { "provider": "similarweb" },
    { "provider": "fiber_ai" }
  ]
}
```

Use the Connect docs for provider IDs, pricing, and field-specific examples:

- Overview: `https://exa.ai/docs/agent/connect/quickstart`
- Combining providers: `https://exa.ai/docs/agent/connect/combining-providers`
- Provider pages: `fiber`, `similarweb`, `baselayer`, `affiliatecom`, `particle`, `financialdatasets`, `jinko`, and `additional-partners` under `/agent/connect/`

## SDK Naming

Python uses snake_case:

```python
from exa_py import Exa

exa = Exa()
run = exa.agent.runs.create(
    query="Find five recently launched developer tools for evaluating AI agents.",
    effort="auto",
    output_schema={
        "type": "object",
        "properties": {
            "tools": {
                "type": "array",
                "maxItems": 5,
                "items": {"type": "object"},
            }
        },
        "required": ["tools"],
    },
)
finished = exa.agent.runs.poll_until_finished(run.id, poll_interval=4000)
if finished.status == "completed":
    print(finished.output.structured)
    print(finished.output.grounding)
else:
    print(finished.status, getattr(finished, "error", None))
```

TypeScript uses camelCase:

```typescript
import Exa from "exa-js";

const exa = new Exa();
const run = await exa.agent.runs.create({
  query: "Find five recently launched developer tools for evaluating AI agents.",
  effort: "auto",
  outputSchema: {
    type: "object",
    properties: {
      tools: {
        type: "array",
        maxItems: 5,
        items: { type: "object" }
      }
    },
    required: ["tools"]
  }
});
const finished = await exa.agent.runs.pollUntilFinished(run.id, { pollInterval: 4000 });
if (finished.status === "completed") {
  console.log(finished.output?.structured);
  console.log(finished.output?.grounding);
} else {
  console.log(finished.status, finished.error);
}
```

## Critical Pitfalls

- Do not stop at create. Wait for a terminal status via polling or SSE events, then check how the run ended before reading `output`; only `completed` carries results.
- Always set `effort` explicitly.
- When building an app, expose `output.grounding` (citations) where relevant in a product's interface.
- Do not use `/agent` for simple low-latency search; prefer `/search`.
- Do not leave unbounded arrays in `outputSchema` when enrichment cost or result size matters.
- Use `input.data` for known rows to enrich; do not paste huge row sets into `query`.
- Use `input.exclusion` for records that should not be surfaced again.
- `previousRunId` must reference a completed run.
- `budget.maxCostDollars` is only accepted with `auto` and `ultra`; a fixed effort plus `budget` is rejected. It is a ceiling, not a guaranteed spend.
