Blog/Developer Tools/

Exa Search API | SandBase

Run Exa neural web search, page contents, and cited answers through one REST API. No separate SDK — one SandBase key, built for agent and RAG workflows.

Dark cinematic render of Exa neural search results, page contents, and a cited answer flowing through one API conduit into an agent core

Exa is a search engine built for AI, not for humans clicking blue links — it does neural, meaning-based retrieval so an agent can find pages by intent, pull their text, and get a cited answer. That makes it a natural fit for retrieval-augmented generation (RAG), research agents, and grounding LLM output in real sources. Wiring it up yourself still means managing another API client and its auth.

The SandBase Exa Search API puts that behind one convention. It runs Exa’s search, contents, and answer capabilities through plain REST — one SandBase API key, no separate SDK. The endpoint API reference is the source of truth for each parameter and the response envelope; the field names below come from calls I ran (tested on 2026-09-27, UTC) and are observed-only — not documented guarantees — so confirm them against a live response, since payloads change over time. Ready to try it? Get a SandBase API key and browse the Exa endpoints.

Key takeaway

  • Three endpoints cover the loop: search (find pages), contents (pull text), and answer (a cited answer).
  • Each call is POST /v1/api/exa/<path> — pass only that endpoint’s params, no SDK, one SANDBASE_API_KEY.
  • The envelope can vary: the reference documents outputs[0].data, but in my calls the payload often arrived under a top-level output object — read either shape.
  • Built for RAG and agents: search by meaning, fetch page text, or get an answer grounded in citations.

A note on this endpoint’s response shape

SandBase endpoints normally return an outputs array whose first item carries the payload under data; the Exa references document this outputs[0].data shape. In my own calls the payload often arrived under a single top-level output object instead (carrying results for search and contents, answer plus citations for answer). Because the envelope can vary, write code that reads either shape: prefer a top-level output, and fall back to outputs[0].data. Confirm the exact envelope for the endpoint you call against its live reference before you build.

Which Exa endpoint do you need?

Your needEndpointInput
Find relevant pages by meaningexa/searchquery
Pull the text of specific pagesexa/contentsids
Get a direct answer with citationsexa/answerquery

SandBase vs. the direct Exa API

Your situationUse
You want one key and one convention across many providersSandBase Exa API (this guide)
You need Exa-specific features or billing directlyExa’s own API and dashboard

SandBase provides a uniform Model API layer over Exa’s capabilities; when you need vendor-specific controls or a direct contract, use Exa’s own API.

What you can get from the Exa API

  • Neural search — exa/search takes a query and returns ranked results, each with a url, title, and publishedDate.
  • Contents — exa/contents takes ids (page URLs, which Exa uses as identifiers) and returns each page’s text.
  • Answer — exa/answer takes a query and returns a synthesized answer with citations.

Check each endpoint’s live API reference for the exact parameters before you build; availability differs by endpoint.

SandBase Exa API page: description, capability tags, and the endpoint list The Exa API page on SandBase — a tagged overview and the endpoint list, each with its path.

Quick start: your first call

SandBase exposes more than one API surface. The catalog may show GET paths under /apis/v1/...; this guide uses the vendor-qualified Model API path on each endpoint’s API reference. Do not swap the HTTP method or URL — follow the reference for the endpoint you choose.

Run a neural search:

import os
import requests

resp = requests.post(
    "https://api.sandbase.ai/v1/api/exa/search",
    headers={
        "Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={"query": "latest advances in AI agents"},
)
resp.raise_for_status()
body = resp.json()
if body.get("status") != "completed":
    error = body.get("error", {})
    raise RuntimeError(error.get("message", "Exa request did not complete"))

# The envelope can vary: prefer a top-level `output`, else fall back to
# the documented `outputs[0].data`. Read the payload defensively.
output = body.get("output")
if output is None and body.get("outputs"):
    output = body["outputs"][0].get("data", {})
output = output or {}
for r in output.get("results", [])[:5]:
    print(r.get("title"), "-", r.get("url"))
curl -X POST https://api.sandbase.ai/v1/api/exa/search \
  -H "Authorization: Bearer $SANDBASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "latest advances in AI agents"}'

The response carries an id, a status, and the model name. The reference documents the payload under outputs[0].data; in my calls it often arrived under a top-level output object — so read either shape. A failed or timeout run instead carries error, so branch on status first. The block below is illustrative, not a documented guarantee — one capture from my search call (search run id 67700c0e-9398-4f89-954d-42a434ed2706, tested on 2026-09-27, UTC); the reference’s business-payload example is intentionally empty, so treat these field names as observed-only and confirm them against a live response:

{
  "id": "67700c0e-9398-4f89-954d-42a434ed2706",
  "model": "exa/search",
  "status": "completed",
  "output": {
    "results": [ { "id": "…", "title": "…", "url": "…", "publishedDate": "…" } ],
    "searchTime": 0.0,
    "resolvedSearchType": "…"
  }
}

Response shapes differ by endpoint — inspect one real response and map the exact path per endpoint.

SandBase API reference for an Exa endpoint, showing the vendor-qualified URL and the response schema The endpoint API reference is the source of truth for each parameter name and response path.

Chaining search → contents → answer

Because every endpoint shares the same auth and a consistent output envelope, an agent can chain the classic RAG loop without special-casing each surface:

  1. Search by meaning. Call exa/search with a query; read the results, each carrying a url (also usable as its id).
  2. Pull the text. Call exa/contents with ids (page URLs or Exa result IDs) to fetch page text; note text is an optional request setting, so confirm the reference for what you request and receive.
  3. Or get a direct answer. Call exa/answer with a query to get a synthesized answer plus citations.

Each step returns the same { id, status, model, output } shape, so your agent branches on status once and reuses the same JSON-reading code across every step.

SandBase Exa endpoint list showing search, contents, and answer endpoints A slice of the Exa endpoint list.

Common use cases

Exa search API for RAG retrieval

Run exa/search with a query to find relevant pages by meaning, then exa/contents with their URLs to pull text for your context window. Input: a query, then page URLs. Output: ranked results, then page text. Endpoints: search, contents.

Exa answer API for grounded responses

Call exa/answer with a question to get a synthesized answer with citations you can surface to users or verify. Input: a query. Output: an answer plus citations. Endpoint: answer.

Exa contents API for text extraction

Pass a set of page URLs as ids to exa/contents to pull their text in one call. Input: ids (URLs). Output: each page’s text. Endpoint: contents.

Why run this at the API layer

Reading Exa through one uniform API means your code depends on named JSON fields and a single response envelope rather than another vendor SDK. Auth is one key, and because every endpoint returns the same { id, status, model, output } shape, retries, logging, and error handling live in one helper you write once and reuse everywhere. Swap a query, swap a set of URLs, and the code path is identical — which is exactly what makes a RAG or research agent composable. Your time goes to what the retrieved sources mean for your task, not to gluing SDKs together. When you need more than these reads, check the live listing for the endpoint that fits and confirm its parameters before wiring it in.

Limitations and boundaries

  • Envelope can vary. The reference documents outputs[0].data; in my calls the payload often arrived under a top-level output object. Read either shape (prefer output, fall back to outputs[0].data) and branch on status.
  • contents takes ids. The ids parameter accepts page URLs or Exa result IDs (typically from a prior search). Page text (text) is an optional request setting — confirm the reference for what you request and what comes back.
  • Fields are observed-only. The reference guarantees the envelope; treat results, text, answer, and citations as observed and confirm live.
  • Rate and volume. Treat responses as best-effort reads; as a client-side resilience measure, retry with backoff on transient errors such as HTTP 429.
  • Verify endpoints against the live reference. Availability and fields can change; confirm before building on a specific endpoint.

FAQ

Do I need a separate Exa API key or SDK? No. You authenticate to SandBase with your SANDBASE_API_KEY and call the Exa endpoints over plain REST — no separate SDK on your side.

Why does this surface return output instead of outputs? The reference documents the payload under outputs[0].data; in my calls it often arrived under a top-level output object with the endpoint’s payload. Because the envelope can vary, read either shape (prefer output, fall back to outputs[0].data) and confirm against the live reference for the endpoint you call.

What’s the difference between search and answer? search returns ranked pages you fetch and process yourself; answer returns a synthesized answer with citations. Use search (+ contents) for RAG, answer when you want a grounded response directly.

Can I use this for RAG? Yes. The search → contents pair is a natural RAG retrieval step: find pages by meaning, pull their text, and feed it to your model. answer covers the case where you want the grounded response directly.

Create a SandBase API key, call search with a query, and inspect the output before you chain into contents or answer. When you are ready: